Materials
Reference for PhysicalMaterial, layers, roughness, phase screens, power-law dispersion, the material protocols and sample types.
The material classes live in witwin.core.material and are exported from witwin.core. For the formulas, see Material model; for examples, see Define materials.
from witwin.core import (
DispersionSpec, FrequencyMaterialSample, MaterialCapabilities, MaterialLayer, MaterialSpec,
PhaseScreen, PhysicalMaterial, PowerLawDispersion, StaticMaterialSample, SurfaceRoughness,
)Continuous fields are typed ScalarLike (float | torch.Tensor). Python values are range-checked at construction and raise ValueError when out of range; tensors must be 0-d and floating point, and their values are not checked.
PhysicalMaterial#
class PhysicalMaterial(
eps_r: ScalarLike = 1.0,
mu_r: ScalarLike = 1.0,
sigma_e: ScalarLike = 0.0,
name: str | None = None,
*,
material_id: MaterialId | int | None = None,
version: int = 0,
thickness_m: ScalarLike = 0.1,
layers=(),
geometry_mode: str = "thin_sheet",
roughness_front: SurfaceRoughness | None = None,
roughness_back: SurfaceRoughness | None = None,
dispersion: DispersionSpec | PowerLawDispersion | None = None,
gain: ScalarLike = 1.0,
scattering_coefficient: ScalarLike = 0.0,
xpd_coefficient: ScalarLike = 0.0,
conductor_model: Literal["finite", "perfect"] = "finite",
)The canonical solver-neutral electromagnetic and surface material. Immutable (frozen dataclass). It implements MaterialSpec.
1.0Relative permittivity. Must be greater than 0.
1.0Relative permeability. Must be greater than 0.
0.0Electric conductivity in S/m. Must be at least 0.
NoneA label. Converted to str.
NoneNone allocates a new ID; an integer reserves it.
0A counter you raise when you replace a material with new Python values under the same material_id. Read by material_version. Must be at least 0.
0.1Slab thickness in metres. Must be greater than 0.
()Layer stack, stored as a tuple. Other item types raise TypeError.
'thin_sheet'"thin_sheet" or "volumetric". Tells a solver how to treat the structure. Other values raise ValueError.
NoneRoughness of the front face.
NoneRoughness of the back face.
NoneFrequency dependence. See Material model.
1.0Gain factor, stored for solvers. Must be at least 0.
0.0Scattering coefficient, stored for solvers. Must be in [0, 1].
0.0Cross-polarization (XPD) coefficient, stored for solvers. Must be in [0, 1].
'finite'"perfect" marks a perfect electric conductor while keeping finite parameters.
PhysicalMaterial.perfect_conductor#
@classmethod
perfect_conductor(name: str | None = "perfect_conductor", **kwargs) -> PhysicalMaterialReturns a PEC material: eps_r=1.0, mu_r=1.0, sigma_e=0.0, conductor_model="perfect". Other keyword arguments go to the constructor. Raises TypeError if you pass conductor_model.
PhysicalMaterial.evaluate_static#
evaluate_static() -> StaticMaterialSampleReturns the bulk values (eps_r, mu_r, sigma_e) unchanged.
PhysicalMaterial.evaluate_at_frequency#
evaluate_at_frequency(frequency: ScalarLike) -> FrequencyMaterialSampleReturns the complex relative permittivity at frequency in Hz: eps_r - j * sigma_e / (2 * pi * f * eps_0) after any power-law scaling, or the value of a custom DispersionSpec. mu_r is returned unchanged; sigma_e is the (scaled) conductivity.
Raises ValueError for a negative Python frequency, for f = 0 with non-zero Python sigma_e, and when tensor inputs differ in device or dtype; TypeError for integer tensors.
from witwin.core import PhysicalMaterial
print(PhysicalMaterial(eps_r=4.0, sigma_e=0.01).evaluate_at_frequency(2.4e9).eps_r)(4-0.07489626493550977j)PhysicalMaterial.capabilities#
capabilities() -> MaterialCapabilitiesReturns the capability flags described in Material model.
MaterialLayer#
@dataclass(frozen=True)
class MaterialLayer:
thickness_m: ScalarLike
eps_r: ScalarLike = 1.0
sigma_e: ScalarLike = 0.0
mu_r: ScalarLike = 1.0
dispersion: DispersionSpec | None = NoneOne homogeneous layer of a stack. thickness_m, eps_r and mu_r must be greater than 0, sigma_e at least 0. dispersion must implement DispersionSpec (a PowerLawDispersion does).
evaluate_at_frequency(frequency_hz) returns a FrequencyMaterialSample for the layer, with the same formula as PhysicalMaterial.
Note
With a PowerLawDispersion, MaterialLayer.evaluate_at_frequency returns the unscaled sigma_e in the sample, while PhysicalMaterial.evaluate_at_frequency returns the scaled one. The complex eps_r is scaled in both.
SurfaceRoughness#
@dataclass(frozen=True)
class SurfaceRoughness:
rms_height_m: ScalarLike
correlation_length_x_m: ScalarLike
correlation_length_y_m: ScalarLike
principal_axis_rad: ScalarLike = 0.0
correlation: str = "gaussian"Gaussian-correlated surface roughness. rms_height_m must be at least 0, both correlation lengths greater than 0. principal_axis_rad is the angle of the x correlation axis. correlation must be "gaussian".
PhaseScreen#
@dataclass(frozen=True)
class PhaseScreen:
height: torch.Tensor
height_scale_m: ScalarLike
height_offset_m: ScalarLike = 0.0
realization_id: int = 0
mode: str = "realization_coherent"
correlation: SurfaceRoughness | None = None
quadrature_tolerance: ScalarLike = 1.0e-4A logical surface height map. Attach it to a Structure (phase_screen=), not to a material. The solver owns its storage and interpretation.
Floating-point height map with H, W > 0. Otherwise ValueError or TypeError.
Height scale in metres. Must be greater than 0.
0.0Height offset in metres.
0Which random realization the map represents. Must be at least 0.
'realization_coherent'"realization_coherent" or "ensemble_bsdf".
NoneThe statistics the map was drawn from, if known.
1.0e-4Tolerance for a solver's numerical integration. Must be greater than 0.
PowerLawDispersion#
@dataclass(frozen=True)
class PowerLawDispersion:
reference_frequency_hz: ScalarLike
eps_r_exponent: ScalarLike = 0.0
sigma_e_exponent: ScalarLike = 0.0Scales a material's base values with frequency: eps_r * (f / f_ref) ** eps_r_exponent and sigma_e * (f / f_ref) ** sigma_e_exponent. reference_frequency_hz must be greater than 0.
scale(frequency_hz) returns the two factors as a tuple. complex_eps() raises TypeError: the dispersion needs the material's base values, so call PhysicalMaterial.evaluate_at_frequency instead.
DispersionSpec#
@runtime_checkable
class DispersionSpec(Protocol):
def complex_eps(self, frequency_hz: ScalarLike) -> ComplexLike: ...Any object with complex_eps(frequency_hz) returning the complex relative permittivity. Core uses the result as is.
MaterialSpec#
@runtime_checkable
class MaterialSpec(Protocol):
name: str | None
def capabilities(self) -> MaterialCapabilities: ...
def evaluate_static(self) -> StaticMaterialSample: ...
def evaluate_at_frequency(self, frequency: ScalarLike) -> FrequencyMaterialSample: ...The contract Structure accepts. See Write your own geometry or material.
MaterialCapabilities#
@dataclass(frozen=True)
class MaterialCapabilities:
conductive: bool = False
perfect_conductor: bool = False
magnetic: bool = False
anisotropic: bool = False
dispersive: bool = False
layered: bool = False
rough: bool = False
phase_screen: bool = FalseFlags a material reports so that a solver can accept or refuse it.
StaticMaterialSample and FrequencyMaterialSample#
@dataclass(frozen=True)
class StaticMaterialSample:
eps_r: ScalarLike
mu_r: ScalarLike = 1.0
sigma_e: ScalarLike = 0.0
@dataclass(frozen=True)
class FrequencyMaterialSample:
eps_r: ScalarLike # complex for PhysicalMaterial
mu_r: ScalarLike = 1.0
sigma_e: ScalarLike = 0.0The values a material returns from evaluate_static() and evaluate_at_frequency().