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

Config#

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.

max_depthintdefault 1

Maximum interactions per path, at least 0. Values above 5 with "reflection" or "transmission" raise RuntimeError.

componentsset[str]default frozenset({'los', 'reflection', 'diffraction'})

Non-empty subset of "los", "reflection", "diffraction", "transmission", "scattering".

max_pathsint | Nonedefault None

Optional positive limit on paths per antenna pair. Without it, the padded path width is the largest actual count over all pairs.

max_paths_scopestrdefault 'per_pair'

Must be "per_pair". Other values raise ValueError.

ad_modestrdefault 'none'

"none", "jvp" or "vjp". Other values raise ValueError.

coupled_pathsbooldefault False

Also trace reflection-diffraction and double-diffraction paths. Requires max_depth >= 2 with "reflection" and "diffraction".

coupled_candidate_limitintdefault 1000000

Candidate bound for coupled paths, 1 to 1,000,000. Exceeding it raises.

scattering_samples_per_m2floatdefault 8.0

Positive rough-surface sample density.

scattering_max_paths_per_pairintdefault 4096

Positive limit on scattering rows per pair.

scattering_power_thresholdfloatdefault 0.0

Non-negative path-gain threshold for scattering rows.

scattering_chain_max_depthintdefault 0

Reflection depth around a scattering vertex, at most 16; 0 disables chains.

scattering_chain_samples_per_m2floatdefault 2.0

Positive chain-vertex sample density.

scattering_chain_max_rowsintdefault 256

Positive limit on chain rows per pair.

isb_boundary_taperbooldefault False

Smooth the field across the incident shadow boundary.

isb_boundary_taper_widthfloatdefault 0.5

Taper width as a multiple of the Fresnel penumbra, in (0, 4].

solve#

solve(scene, config, *, reference_frequency_hz) -> PathResult
scenewitwin.core.Scene | witwin.core.SceneSnapshotrequired

The world. A ReceiverGrid is expanded so that every grid sample becomes one receiver.

configpath.Configrequired

This module's Config.

reference_frequency_hzfloat | torch.Tensorrequired

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

FieldType and shapeMeaning
acomplex64, (R, Ra, T, Ta, P, time)Complex path coefficient for unit excitation. It does not include power_w.
taufloat32, (R, Ra, T, Ta, P)Delay in seconds; -1 for padding.
theta_t, phi_tfloat32, (R, Ra, T, Ta, P)Departure zenith and azimuth angles in radians, in the world frame.
theta_r, phi_rfloat32, (R, Ra, T, Ta, P)Arrival zenith and azimuth angles, pointing from the receiver toward where the wave comes from.
validbool, (R, Ra, T, Ta, P)False for padding.
interaction_typeint32, (R, Ra, T, Ta, P, D)InteractionType flag of each interaction.
primitive_id, material_idint32, (R, Ra, T, Ta, P, D)Triangle and material of each interaction.
position, normalfloat32, (R, Ra, T, Ta, P, D, 3)Interaction points and surface normals.
num_pathsint32, (R, Ra, T, Ta)Actual number of paths per antenna pair.
metadatadictSolver name, schema, phase and array conventions, coefficient semantics, counts.
field_xyzcomplex64, (R, Ra, T, Ta, P, 3)Complex field vector in world coordinates.
field_directionfloat32, (R, Ra, T, Ta, P, 3)Field propagation direction.
tx_weights, rx_weightscomplex64 or NoneDefault array weights taken from the endpoints' weights.

Properties#

PropertyValue
num_rx, num_rx_ant, num_tx, num_tx_antR, Ra, T, Ta
max_num_pathsP
num_time_stepsLast dimension of a
max_depthD
path_shape(R, Ra, T, Ta, P)
path_count_shape(R, Ra, T, Ta)
rx_id, tx_idReceiver and transmitter index of every path slot, shaped like valid
path_length_mtau * c; -1 for padding
types, vertices, normals, objectsAliases 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) -> Tensor

Channel frequency response sum over paths of a * exp(-j * 2 * pi * tau * f).

frequenciestorch.Tensorrequired

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) -> Tensor

Discrete-time channel taps: each path is added to tap round(tau * bandwidth); paths beyond num_taps are dropped.

bandwidthfloatrequired

Sampling bandwidth in hertz, positive.

num_tapsintrequired

Number of taps, positive.

Returns complex64, (R, Ra, T, Ta, time, num_taps).

beamform#

beamform(*, tx_weights=None, rx_weights=None) -> BeamformedPathResult

Applies transmit precoding and receive combining. The coefficient of each path becomes conj(rx_weight) * a * tx_weight. The PathResult itself is unchanged.

tx_weightstorch.Tensor | Nonedefault None

Complex weights (T, Ta), or (Ta,) when there is one transmitter. None uses PathResult.tx_weights.

rx_weightstorch.Tensor | Nonedefault None

Complex 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:

MethodShape
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) -> PathResult

Keeps 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) -> PathResult

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

MemberValue
NONE0
REFLECTION1
DIFFRACTION2
TRANSMISSION4
SCATTERING8
has_reflection = (paths.interaction_type & int(InteractionType.REFLECTION)) != 0

RaggedPathSoA#

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.

FieldType and shape
num_rx, num_rx_ant, num_tx, num_tx_ant, num_time_stepsint
pair_offsetsint64, (pair_count + 1,)
rx_id, rx_ant_id, tx_id, tx_ant_idint32, (K,)
fieldcomplex64, (K, time)
delay_s, theta_t, phi_t, theta_r, phi_rfloat32, (K,)
interaction_type, primitive_id, material_idint32, (K, D)
position, normalfloat32, (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.