witwin.channel.deterministic
API reference for the deterministic solver - Config, solve, Result and the compact PathTable.
The deterministic solver finds every path up to the configured depth and adds their complex fields. Use it for repeatable link values, radio maps and inverse problems.
from witwin.channel.deterministic import Config, PathTable, Result, solveConfig#
Config(
max_depth=1,
max_diffraction_order=1,
components=frozenset({"los", "reflection", "diffraction"}),
coherent=True,
return_field=True,
export_paths=False,
max_paths=None,
max_paths_scope="global",
sort_key="receiver_transmitter_depth_component",
diagnostics=False,
ad_mode="none",
coupled_paths=False,
coupled_candidate_limit=1_000_000,
scattering_samples_per_m2=8.0,
scattering_max_paths_per_pair=4096,
scattering_power_threshold=0.0,
scattering_coherent=False,
scattering_chain_max_depth=0,
scattering_chain_samples_per_m2=2.0,
scattering_chain_max_rows=256,
isb_boundary_taper=False,
isb_boundary_taper_width=0.5,
)A frozen dataclass. All arguments are keyword-friendly; the constructor validates them and raises on an invalid value.
1Maximum number of interactions per path. Must be at least 0. With "reflection" or "transmission" requested, values above 5 raise RuntimeError.
1Maximum number of diffractions per path, 0 or 1. Negative values raise ValueError; values above 1 raise RuntimeError.
frozenset({'los', 'reflection', 'diffraction'})Non-empty subset of "los", "reflection", "diffraction", "transmission" and "scattering". Accepts a set, frozenset, tuple or list; stored as a frozenset.
TrueTrue adds complex fields, so path_gain shows interference. False adds powers.
TrueFalse skips the complex total field; Result.field is then an empty tensor.
FalseTrue fills Result.paths with a PathTable of every path.
NoneOptional positive limit on exported path rows.
'global'"global" applies max_paths to the whole table; "per_pair" applies it to each transmitter-receiver pair.
'receiver_transmitter_depth_component'Order of rows in the PathTable. This is the only accepted value.
FalseTrue fills Result.diagnostics with counters such as path_count, component_counts and native_launch_count.
'none'"none", "jvp" or "vjp". See Differentiability. Other values raise RuntimeError.
FalseAlso trace reflection-then-diffraction, diffraction-then-reflection and double-diffraction paths. Requires max_depth >= 2 and both "reflection" and "diffraction". Adds a "coupled" entry to component_power and component_fields.
1000000Upper bound on coupled-path candidates, between 1 and 1,000,000. Exceeding it during a solve raises instead of dropping candidates.
8.0Positive sample density on rough surfaces, in samples per square metre.
4096Positive limit on scattering rows per transmitter-receiver pair.
0.0Non-negative path-gain threshold below which scattering rows are not exported.
FalseSum scattering rows as complex fields instead of powers. Requires "scattering" in components. Physical only for realization-coherent phase screens.
0Total reflection depth around a scattering vertex, at most 16. 0 disables scattering chains; a positive value requires "scattering".
2.0Positive sample density for scattering-chain vertices.
256Positive limit on retained chain rows per pair.
FalseSmooth the field across the incident shadow boundary (ISB), where a path starts or stops being visible, to reduce the step in the result. Off by default.
0.5Width of the taper as a multiple of the Fresnel penumbra, in (0, 4].
config = Config(max_depth=2, components={"los", "reflection"}, export_paths=True)solve#
solve(scene, config, *, reference_frequency_hz) -> ResultCompiles the scene (or reuses the cached compilation), discovers paths and accumulates their fields.
The world: structures and endpoints.
This module's Config. Another solver's Config raises.
Carrier frequency in hertz, positive. A 0-d float64 tensor with requires_grad=True makes the result differentiable with respect to frequency.
Returns a Result.
Raises on missing CUDA or native extension, an unsupported component and ad_mode combination, a capacity overflow, or an invalid scene. No partial result is returned.
result = solve(scene, Config(max_depth=1, components={"los", "reflection"}), reference_frequency_hz=3.0e9)
print(result.path_gain.shape) # (T, R) for point receiversResult#
A frozen dataclass.
| Field | Type and shape | Meaning |
|---|---|---|
path_gain | float32, (T, R) for point receivers, (T, columns, rows) for a ReceiverGrid | Received power abs(field)**2 (coherent) or the power sum (incoherent). Includes the transmitter's power_w. |
field | complex64, same shape as path_gain | Total complex field. Empty (0,) when return_field=False. |
component_power | dict[str, Tensor] | Power per component, each shaped like path_gain. Always has "los", "reflection" and "diffraction", plus "transmission", "scattering" or "coupled" when requested. A component that was not requested is zero. |
component_fields | dict[str, Tensor] | Complex field per component, same keys and shapes. |
paths | PathTable or None | The path table when export_paths=True. |
metadata | dict | Effective configuration, phase convention, coefficient semantics, counts and timing. |
diagnostics | dict or None | Counters when diagnostics=True. |
T and R count transmitters and receivers in the order they appear in scene.endpoints.
PathTable#
One row per path, K rows in total, with no padding. Rows are sorted by receiver, then transmitter, then depth, then component. D is the interaction width of the table.
| Field | Type and shape | Meaning |
|---|---|---|
valid | bool, (K,) | Row validity; True for every row of a successful solve. |
tx_id, rx_id | int32, (K,) | Transmitter and receiver index (position in the endpoint order, starting at 0), not the stable antenna ID. |
depth | int32, (K,) | Number of interactions. |
component_id | int32, (K,) | Component code; see Solvers and path types. |
primitive_id, edge_id, material_id | int32, (K,) | Triangle, edge and material of the first interaction. |
path_length_m, delay_s, path_gain | float32, (K,) | Length, delay and power of the path. |
interaction_position, interaction_normal | float32, (K, 3) | First interaction point and surface normal. |
primitive_sequence, material_sequence | int32, (K, D) | Triangles and materials of every interaction. |
interaction_positions, interaction_normals | float32, (K, D, 3) | Every interaction point and normal. |
field_real, field_imag | float32, (K,) | Real and imaginary part of the received scalar field. |
coefficient | complex64, (K,) | Complex path coefficient. |
field_xyz | complex64, (K, 3) | Complex field vector in world coordinates. |
field_direction | float32, (K, 3) | Arrival direction of the field. |
phase_rad | float32, (K,) | Phase at the reference frequency. |
interaction_count | int32, (K,) | Actual number of interactions. |
result = solve(scene, Config(max_depth=1, components={"los", "reflection"}, export_paths=True), reference_frequency_hz=3.0e9)
paths = result.paths
print(paths.delay_s.shape) # (K,)
print(paths.interaction_positions.shape) # (K, D, 3)