witwin.channel.path
API reference for the path solver - Config, solve, PathResult with CIR, CFR, taps and beamforming, InteractionType and RaggedPathSoA.
The path solver returns every propagation path of every antenna pair, with its complex coefficient, delay, angles and interactions. Use it for channel models, delay spread, angle statistics and antenna arrays.
from witwin.channel.path import Config, InteractionType, PathResult, RaggedPathSoA, solveConfig#
Config(
max_depth=1,
components=frozenset({"los", "reflection", "diffraction"}),
max_paths=None,
max_paths_scope="per_pair",
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_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. The shared arguments mean the same as in the deterministic Config.
1Maximum interactions per path, at least 0. Values above 5 with "reflection" or "transmission" raise RuntimeError.
frozenset({'los', 'reflection', 'diffraction'})Non-empty subset of "los", "reflection", "diffraction", "transmission", "scattering".
NoneOptional positive limit on paths per antenna pair. Without it, the padded path width is the largest actual count over all pairs.
'per_pair'Must be "per_pair". Other values raise ValueError.
'none'"none", "jvp" or "vjp". Other values raise ValueError.
FalseAlso trace reflection-diffraction and double-diffraction paths. Requires max_depth >= 2 with "reflection" and "diffraction".
1000000Candidate bound for coupled paths, 1 to 1,000,000. Exceeding it raises.
8.0Positive rough-surface sample density.
4096Positive limit on scattering rows per pair.
0.0Non-negative path-gain threshold for scattering rows.
0Reflection depth around a scattering vertex, at most 16; 0 disables chains.
2.0Positive chain-vertex sample density.
256Positive limit on chain rows per pair.
FalseSmooth the field across the incident shadow boundary.
0.5Taper width as a multiple of the Fresnel penumbra, in (0, 4].
solve#
solve(scene, config, *, reference_frequency_hz) -> PathResultThe world. A ReceiverGrid is expanded so that every grid sample becomes one receiver.
This module's Config.
Carrier frequency in hertz. A 0-d float64 tensor with requires_grad=True enables frequency gradients.
Returns a PathResult. Raises like the other solvers: no partial result on any failure.
PathResult#
A frozen dataclass with explicit antenna dimensions. In the shapes below, R and T count receivers and transmitters, Ra and Ta their antenna elements, P the padded path width, D the interaction width and time the number of time steps (1 for a static scene).
| Field | Type and shape | Meaning |
|---|---|---|
a | complex64, (R, Ra, T, Ta, P, time) | Complex path coefficient for unit excitation. It does not include power_w. |
tau | float32, (R, Ra, T, Ta, P) | Delay in seconds; -1 for padding. |
theta_t, phi_t | float32, (R, Ra, T, Ta, P) | Departure zenith and azimuth angles in radians, in the world frame. |
theta_r, phi_r | float32, (R, Ra, T, Ta, P) | Arrival zenith and azimuth angles, pointing from the receiver toward where the wave comes from. |
valid | bool, (R, Ra, T, Ta, P) | False for padding. |
interaction_type | int32, (R, Ra, T, Ta, P, D) | InteractionType flag of each interaction. |
primitive_id, material_id | int32, (R, Ra, T, Ta, P, D) | Triangle and material of each interaction. |
position, normal | float32, (R, Ra, T, Ta, P, D, 3) | Interaction points and surface normals. |
num_paths | int32, (R, Ra, T, Ta) | Actual number of paths per antenna pair. |
metadata | dict | Solver name, schema, phase and array conventions, coefficient semantics, counts. |
field_xyz | complex64, (R, Ra, T, Ta, P, 3) | Complex field vector in world coordinates. |
field_direction | float32, (R, Ra, T, Ta, P, 3) | Field propagation direction. |
tx_weights, rx_weights | complex64 or None | Default array weights taken from the endpoints' weights. |
Properties#
| Property | Value |
|---|---|
num_rx, num_rx_ant, num_tx, num_tx_ant | R, Ra, T, Ta |
max_num_paths | P |
num_time_steps | Last dimension of a |
max_depth | D |
path_shape | (R, Ra, T, Ta, P) |
path_count_shape | (R, Ra, T, Ta) |
rx_id, tx_id | Receiver and transmitter index of every path slot, shaped like valid |
path_length_m | tau * c; -1 for padding |
types, vertices, normals, objects | Aliases of interaction_type, position, normal, primitive_id |
cir#
cir(*, normalize_delays=True) -> tuple[Tensor, Tensor]Returns (a, tau) with padding coefficients set to zero. With normalize_delays=True, each link's earliest path is shifted to delay 0.
cfr#
cfr(frequencies, *, normalize_delays=True) -> TensorChannel frequency response sum over paths of a * exp(-j * 2 * pi * tau * f).
1-D tensor of frequencies in hertz, usually offsets from the carrier. Other shapes raise ValueError.
Returns complex64, (R, Ra, T, Ta, time, F).
taps#
taps(bandwidth, num_taps, *, normalize_delays=True) -> TensorDiscrete-time channel taps: each path is added to tap round(tau * bandwidth); paths beyond num_taps are dropped.
Sampling bandwidth in hertz, positive.
Number of taps, positive.
Returns complex64, (R, Ra, T, Ta, time, num_taps).
beamform#
beamform(*, tx_weights=None, rx_weights=None) -> BeamformedPathResultApplies transmit precoding and receive combining. The coefficient of each path becomes conj(rx_weight) * a * tx_weight. The PathResult itself is unchanged.
NoneComplex weights (T, Ta), or (Ta,) when there is one transmitter. None uses PathResult.tx_weights.
NoneComplex weights (R, Ra), or (Ra,) when there is one receiver. None uses PathResult.rx_weights.
Both weights must be available, explicitly or from the endpoints; otherwise ValueError. The returned view has the same three methods with array dimensions folded:
| Method | Shape |
|---|---|
view.cir() | (R, T, Ra * Ta * P, time) coefficients and (R, T, Ra * Ta * P) delays, one entry per element-pair path, not yet summed |
view.cfr(frequencies) | (R, T, time, F), summed over paths and elements |
view.taps(bandwidth, num_taps) | (R, T, time, num_taps) |
filter_by_type#
filter_by_type(*interaction_types) -> PathResultKeeps the paths that contain any of the given InteractionType values and compacts the padding. InteractionType.NONE selects line of sight. With no argument it returns the same object.
reflected = paths.filter_by_type(InteractionType.REFLECTION)from_ragged#
PathResult.from_ragged(ragged, *, max_paths_per_pair=None, minimum_path_width=0, metadata=None) -> PathResultBuilds the padded layout from a RaggedPathSoA. max_paths_per_pair fixes the padded width (counts must not exceed it); minimum_path_width sets a lower bound on it.
InteractionType#
enum.IntFlag for PathResult.interaction_type.
| Member | Value |
|---|---|
NONE | 0 |
REFLECTION | 1 |
DIFFRACTION | 2 |
TRANSMISSION | 4 |
SCATTERING | 8 |
has_reflection = (paths.interaction_type & int(InteractionType.REFLECTION)) != 0RaggedPathSoA#
Pair-grouped path storage without padding: K rows sorted by pair, with pair_offsets of length pair_count + 1 marking where each pair starts. Most users never need it; it is the input of PathResult.from_ragged.
| Field | Type and shape |
|---|---|
num_rx, num_rx_ant, num_tx, num_tx_ant, num_time_steps | int |
pair_offsets | int64, (pair_count + 1,) |
rx_id, rx_ant_id, tx_id, tx_ant_id | int32, (K,) |
field | complex64, (K, time) |
delay_s, theta_t, phi_t, theta_r, phi_r | float32, (K,) |
interaction_type, primitive_id, material_id | int32, (K, D) |
position, normal | float32, (K, D, 3) |
Properties: path_count (K), pair_count (R * Ra * T * Ta), max_depth (D).
RaggedPathSoA.from_flat(*, num_rx, num_rx_ant, num_tx, num_tx_ant, rx_id, tx_id, field, delay_s, theta_t, phi_t, theta_r, phi_r, interaction_type, primitive_id, material_id, position, normal, rx_ant_id=None, tx_ant_id=None, max_paths_per_pair=None) sorts flat rows by pair (receiver major) and builds pair_offsets. Missing antenna IDs become 0; a positive max_paths_per_pair keeps the first rows of each pair. Out-of-range IDs or wrong shapes raise ValueError.