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, solve

Config#

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.

max_depthintdefault 1

Maximum number of interactions per path. Must be at least 0. With "reflection" or "transmission" requested, values above 5 raise RuntimeError.

max_diffraction_orderintdefault 1

Maximum number of diffractions per path, 0 or 1. Negative values raise ValueError; values above 1 raise RuntimeError.

componentsset[str]default 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.

coherentbooldefault True

True adds complex fields, so path_gain shows interference. False adds powers.

return_fieldbooldefault True

False skips the complex total field; Result.field is then an empty tensor.

export_pathsbooldefault False

True fills Result.paths with a PathTable of every path.

max_pathsint | Nonedefault None

Optional positive limit on exported path rows.

max_paths_scopestrdefault 'global'

"global" applies max_paths to the whole table; "per_pair" applies it to each transmitter-receiver pair.

sort_keystrdefault 'receiver_transmitter_depth_component'

Order of rows in the PathTable. This is the only accepted value.

diagnosticsbooldefault False

True fills Result.diagnostics with counters such as path_count, component_counts and native_launch_count.

ad_modestrdefault 'none'

"none", "jvp" or "vjp". See Differentiability. Other values raise RuntimeError.

coupled_pathsbooldefault False

Also 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.

coupled_candidate_limitintdefault 1000000

Upper bound on coupled-path candidates, between 1 and 1,000,000. Exceeding it during a solve raises instead of dropping candidates.

scattering_samples_per_m2floatdefault 8.0

Positive sample density on rough surfaces, in samples per square metre.

scattering_max_paths_per_pairintdefault 4096

Positive limit on scattering rows per transmitter-receiver pair.

scattering_power_thresholdfloatdefault 0.0

Non-negative path-gain threshold below which scattering rows are not exported.

scattering_coherentbooldefault False

Sum scattering rows as complex fields instead of powers. Requires "scattering" in components. Physical only for realization-coherent phase screens.

scattering_chain_max_depthintdefault 0

Total reflection depth around a scattering vertex, at most 16. 0 disables scattering chains; a positive value requires "scattering".

scattering_chain_samples_per_m2floatdefault 2.0

Positive sample density for scattering-chain vertices.

scattering_chain_max_rowsintdefault 256

Positive limit on retained chain rows per pair.

isb_boundary_taperbooldefault False

Smooth 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.

isb_boundary_taper_widthfloatdefault 0.5

Width 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) -> Result

Compiles the scene (or reuses the cached compilation), discovers paths and accumulates their fields.

scenewitwin.core.Scene | witwin.core.SceneSnapshotrequired

The world: structures and endpoints.

configdeterministic.Configrequired

This module's Config. Another solver's Config raises.

reference_frequency_hzfloat | torch.Tensorrequired

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 receivers

Result#

A frozen dataclass.

FieldType and shapeMeaning
path_gainfloat32, (T, R) for point receivers, (T, columns, rows) for a ReceiverGridReceived power abs(field)**2 (coherent) or the power sum (incoherent). Includes the transmitter's power_w.
fieldcomplex64, same shape as path_gainTotal complex field. Empty (0,) when return_field=False.
component_powerdict[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_fieldsdict[str, Tensor]Complex field per component, same keys and shapes.
pathsPathTable or NoneThe path table when export_paths=True.
metadatadictEffective configuration, phase convention, coefficient semantics, counts and timing.
diagnosticsdict or NoneCounters 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.

FieldType and shapeMeaning
validbool, (K,)Row validity; True for every row of a successful solve.
tx_id, rx_idint32, (K,)Transmitter and receiver index (position in the endpoint order, starting at 0), not the stable antenna ID.
depthint32, (K,)Number of interactions.
component_idint32, (K,)Component code; see Solvers and path types.
primitive_id, edge_id, material_idint32, (K,)Triangle, edge and material of the first interaction.
path_length_m, delay_s, path_gainfloat32, (K,)Length, delay and power of the path.
interaction_position, interaction_normalfloat32, (K, 3)First interaction point and surface normal.
primitive_sequence, material_sequenceint32, (K, D)Triangles and materials of every interaction.
interaction_positions, interaction_normalsfloat32, (K, D, 3)Every interaction point and normal.
field_real, field_imagfloat32, (K,)Real and imaginary part of the received scalar field.
coefficientcomplex64, (K,)Complex path coefficient.
field_xyzcomplex64, (K, 3)Complex field vector in world coordinates.
field_directionfloat32, (K, 3)Arrival direction of the field.
phase_radfloat32, (K,)Phase at the reference frequency.
interaction_countint32, (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)