Materials
How Channel uses witwin.core PhysicalMaterial - permittivity, conductivity, layered walls, perfect conductors, dispersion and rough surfaces.
Every structure carries one witwin.core.PhysicalMaterial. Channel evaluates it at the solve frequency and uses it for Fresnel reflection, wall transmission, edge diffraction and rough-surface scattering. This page lists the fields Channel reads and what each one changes.
A basic dielectric#
from witwin.core import PhysicalMaterial
concrete = PhysicalMaterial(eps_r=5.24, sigma_e=0.0462, name="concrete")| Field | Default | Meaning |
|---|---|---|
eps_r | 1.0 | Relative permittivity (real part). |
sigma_e | 0.0 | Electric conductivity in S/m. Channel forms the complex permittivity eps_r - j * sigma_e / (2 * pi * f * eps0) at the solve frequency f. |
mu_r | 1.0 | Relative permeability. It is not a differentiable input. |
thickness_m | 0.1 | Wall thickness in metres, used when the material has no explicit layers. Must be positive. |
name | None | Label for reports. |
The numbers in the example are close to the ITU-R P.2040 concrete values at 3.5 GHz. Channel does not ship a material library; take values from ITU-R P.2040 or a measurement for your band. The WiTwin Studio plugin has presets.
Walls: thin sheets and layers#
Channel models a wall as a planar layer stack attached to a surface. The geometry_mode field selects how a surface is treated:
"thin_sheet"(default): the surface stands for a slab. Reflection and transmission use the layer stack, so an open mesh such as a single wall can be both reflective and penetrable."volumetric": the mesh is a boundary of a solid body. Rays reflect and diffract at it, but the"transmission"component does not pass through it.
Without layers, the stack is one layer built from thickness_m, eps_r, sigma_e and mu_r. For a composite wall, pass MaterialLayer objects in stack order. The example is symmetric, so it behaves the same from both sides:
from witwin.core import MaterialLayer, PhysicalMaterial
drywall_wall = PhysicalMaterial(
name="insulated wall",
layers=(
MaterialLayer(0.0125, eps_r=2.7, sigma_e=0.005),
MaterialLayer(0.09, eps_r=1.2),
MaterialLayer(0.0125, eps_r=2.7, sigma_e=0.005),
),
)MaterialLayer(thickness_m, eps_r=1.0, sigma_e=0.0, mu_r=1.0, dispersion=None) takes the same units as the material. Channel computes the reflection and transmission coefficients of the whole stack for both polarizations, including the internal multiple reflections. Wall transmission is opt-in: add "transmission" to the solver's components. The walls and materials guide shows a wall that blocks line of sight.
Perfect conductors#
metal = PhysicalMaterial.perfect_conductor()PhysicalMaterial.perfect_conductor(name="perfect_conductor", **kw) sets conductor_model="perfect". Channel treats it as an explicit perfect electric conductor (reflection coefficient of magnitude 1, no transmission) rather than approximating it with a large conductivity. Use it for metal surfaces and for comparisons with analytic results.
Frequency dependence#
A material is evaluated once, at the reference_frequency_hz of the solve, when Channel compiles the scene. Conductivity already makes the complex permittivity frequency dependent. For a material whose eps_r and sigma_e themselves change with frequency, attach a power-law dispersion:
from witwin.core import PhysicalMaterial, PowerLawDispersion
glass = PhysicalMaterial(
eps_r=6.31,
sigma_e=0.0036,
dispersion=PowerLawDispersion(1.0e9, eps_r_exponent=0.0, sigma_e_exponent=1.3394),
)PowerLawDispersion(reference_frequency_hz, eps_r_exponent=0.0, sigma_e_exponent=0.0) scales the base values by (f / reference_frequency_hz) ** exponent. This is the form ITU-R P.2040 uses (eps_r = a * f**b, sigma = c * f**d).
Because materials are frozen at the compile frequency, two features refuse dispersive materials: gradients with respect to frequency, and wideband frequency offsets in the propagation consumer. Both raise instead of returning a result that misses the material's frequency dependence.
Rough surfaces and scattering#
Diffuse scattering needs two things: a roughness description on the material, and "scattering" in the solver's components.
from witwin.core import PhysicalMaterial, SurfaceRoughness
rough = SurfaceRoughness(
rms_height_m=0.004,
correlation_length_x_m=0.05,
correlation_length_y_m=0.05,
)
brick = PhysicalMaterial(eps_r=3.91, sigma_e=0.0238, roughness_front=rough)| Type | Fields |
|---|---|
SurfaceRoughness | rms_height_m, correlation_length_x_m, correlation_length_y_m, principal_axis_rad=0.0, correlation="gaussian" (the only accepted value). Assign it to roughness_front or roughness_back. |
PhaseScreen | A sampled height map for one surface: height (a floating-point (H, W) tensor), height_scale_m, height_offset_m=0.0, realization_id=0, mode="realization_coherent" or "ensemble_bsdf", correlation=None, quadrature_tolerance=1e-4. Assign it to Structure(phase_screen=...). |
SurfaceRoughness describes the surface statistically; Channel evaluates a Kirchhoff scattering model from it. A PhaseScreen describes one realization of the surface. In "realization_coherent" mode the structure also needs texture coordinates (Structure(uv=..., face_uv=...)) so Channel can map the height map onto the triangles. Only realization-coherent screens make coherent scattering (scattering_coherent=True in the deterministic solver) physical.
Scattering is not differentiable in any solver.
Other material fields#
| Field | Default | Meaning |
|---|---|---|
gain | 1.0 | Non-negative per-material scalar weight carried into the compiled material table. It is one of the differentiable material inputs. |
scattering_coefficient | 0.0 | Value in [0, 1] carried into the compiled material table. |
xpd_coefficient | 0.0 | Cross-polarization discrimination coefficient in [0, 1], carried into the compiled material table. |
roughness_front, roughness_back | None | SurfaceRoughness for the two sides of the surface. |
material_id, version | generated, 0 | Stable identity. Results report material_id per path. |
Materials as tensors#
Any scalar field can be a 0-d floating-point tensor instead of a Python number. A tensor with requires_grad=True makes the solver result differentiable with respect to that field:
import torch
eps_r = torch.tensor(4.0, requires_grad=True)
wall_material = PhysicalMaterial(eps_r=eps_r, sigma_e=0.02)See Differentiability for which fields and solvers support gradients.