"""
Material thermal property definitions for CFAST simulations.
This module provides the MaterialProperties class for defining
thermophysical properties of materials used in CFAST simulations.
"""
from __future__ import annotations
from typing import Any
from .utils.namelist import NamelistRecord
from .utils.theme import build_card
[docs]
class MaterialProperties:
"""
Defines thermophysical properties of materials used for compartment surfaces or targets.
CFAST and CEdit do not include predefined thermal properties for compartment materials.
Thus, the user needs to define materials for use within a specific simulation. These may
be from other simulations or input directly from reference sources or test results.
The thermophysical properties are specified at one condition of temperature, humidity, etc.
Only a single layer per boundary is allowed (some previous versions allowed up to three).
Parameters
----------
id : str
A one-word (no more than 8 characters) unique identifier for the material. This
identifier should not contain any spaces and is used in other CFAST inputs to
identify the particular material referenced.
material : str
A descriptive name for the material.
conductivity : float, optional
Thermal conductivity for the material. Default units: kW/(m·°C) or kW/(m·K).
specific_heat : float, optional
Specific heat for the material. Default units: kJ/(kg·°C) or kJ/(kg·K).
density : float, optional
Density for the material. Default units: kg/m³.
thickness : float, optional
Thickness of the material. Note that if two materials with identical thermal
properties but with different thicknesses are desired, two separate materials
must be defined. Default units: m.
emissivity : float, optional
Emissivity of the material surface. This is the fraction of radiation that is
absorbed by the material. Default units: none, default value: 0.9.
Notes
-----
The thermophysical properties are specified at one condition of temperature, humidity, etc.
Values are assumed constant (no temperature dependence). Responsibility for data accuracy
lies with the user.
Examples
--------
Create a gypsum wallboard material:
>>> gypsum = MaterialProperties(
... id="GYPSUM",
... material="Gypsum Wallboard",
... conductivity=0.17,
... density=930,
... specific_heat=1.09,
... thickness=0.016,
... emissivity=0.9,
... )
>>> print(gypsum.id)
GYPSUM
"""
def __init__(
self,
id: str,
material: str,
conductivity: float | None = None,
density: float | None = None,
specific_heat: float | None = None,
thickness: float | None = None,
emissivity: float | None = 0.9,
):
self.id = id
self.material = material
self.conductivity = conductivity
self.density = density
self.specific_heat = specific_heat
self.thickness = thickness
self.emissivity = emissivity
self._validate()
def _validate(self) -> None:
"""Validate the current state of the material property attributes.
Raises
------
TypeError
If id is not a string or exceeds 16 characters.
"""
if not isinstance(self.id, str) or len(self.id) > 16:
raise TypeError("id must be a string with no more than 16 characters.")
def __repr__(self) -> str:
"""Return a detailed string representation of the MaterialProperties."""
return (
f"MaterialProperties("
f"id='{self.id}', material='{self.material}', "
f"conductivity={self.conductivity}, density={self.density}, "
f"specific_heat={self.specific_heat}, thickness={self.thickness}, "
f"emissivity={self.emissivity}"
f")"
)
def __str__(self) -> str:
"""Return a user-friendly string representation of the MaterialProperties."""
return (
f"Material '{self.id}' ({self.material}): "
f"k={self.conductivity}, ρ={self.density}, c={self.specific_heat}, "
f"t={self.thickness}, ε={self.emissivity}"
)
def _repr_html_(self) -> str:
"""Return an HTML representation for Jupyter/interactive environments."""
body_html = f"""
<div class="pycfast-card-grid">
<div><strong>Conductivity:</strong> {getattr(self, "conductivity", "N/A")} W/m·K</div>
<div><strong>Density:</strong> {getattr(self, "density", "N/A")} kg/m³</div>
<div><strong>Specific Heat:</strong> {getattr(self, "specific_heat", "N/A")} kJ/kg·K</div>
<div><strong>Thickness:</strong> {getattr(self, "thickness", "N/A")} m</div>
<div><strong>Emissivity:</strong> {getattr(self, "emissivity", "N/A")}</div>
</div>
"""
return build_card(
icon="🧱",
gradient="linear-gradient(135deg, #2d3436, #636e72)",
title=f"Material: {self.id}",
subtitle=f"<strong>{getattr(self, 'material', 'Material')}</strong>",
accent_color="#636e72",
body_html=body_html,
)
def __getitem__(self, key: str) -> Any:
"""Get material property by name for dictionary-like access."""
if not hasattr(self, key):
raise KeyError(f"Property '{key}' not found in MaterialProperties.")
return getattr(self, key)
def __setitem__(self, key: str, value: Any) -> None:
"""Set material property by name for dictionary-like assignment.
Validates the object state after setting the attribute to ensure
all constraints are still satisfied.
Raises
------
KeyError
If the property does not exist.
TypeError
If setting this value would violate object constraints.
"""
if not hasattr(self, key):
raise KeyError(
f"Cannot set '{key}'. Property does not exist in MaterialProperties."
)
old_value = getattr(self, key)
setattr(self, key, value)
try:
self._validate()
except Exception:
setattr(self, key, old_value)
raise