witwin.channel.propagation.consumer
API reference for the propagation consumer - request types, evaluate, fixed-topology replay, time-varying evaluation, wideband offsets and constants.
The propagation consumer is the compact interface other WiTwin solvers (for example Radar) use to get paths from Channel. Instead of a solver result, it returns exactly K path rows: their discrete topology, continuous geometry and a transport coefficient. It can also replay a discovered topology for moved endpoints, many time slots or many frequencies without searching for paths again.
from witwin.channel.propagation import consumer
from witwin.channel.scene import compileResult and topology types are on consumer types. The fixed-topology replay guide walks through a complete example.
Constants#
| Name | Value |
|---|---|
CONTRACT_VERSION | 6 |
MAX_DEPTH | 5 |
COMPONENTS | frozenset({"los", "reflection", "transmission", "diffraction"}) |
RESPONSES | frozenset({"scalar_transport", "complex3_transport", "polarimetric_transport"}) |
TOPOLOGY_MODES | frozenset({"discover"}) |
AD_MODES | frozenset({"none", "jvp", "vjp"}) |
PropagationComponent, PropagationResponse, PropagationTopologyMode, PropagationAdMode | Literal type aliases of the four sets above |
"scattering" is not a consumer component. Membership in these sets does not mean every combination works: ask capabilities().
EndpointBatch#
EndpointBatch(stable_ids, positions_m, polarizations, polarization_basis=None, powers_w=None)A batch of N point endpoints. All tensors must be contiguous CUDA tensors on one device.
int64, (N,). Stable endpoint IDs, chosen by you; they are echoed in the topology.
float32, (N, 3) world positions in metres.
float32, (N, 3) polarization vectors.
Nonefloat32, (N, 2, 3) transverse basis. Required for "polarimetric_transport".
Nonefloat32, (N,) transmit power in watts. Required for sources, not allowed for sinks.
Properties: count (N) and device. Raises TypeError or ValueError on a dtype, shape, device or contiguity error.
PropagationRequest#
PropagationRequest(sources, sinks, reference_frequency_hz, components, max_depth, response, topology_mode, ad_mode, max_paths=None)A path-discovery request. Every argument except max_paths is required.
Transmitting endpoints, with powers_w.
Receiving endpoints, without powers_w.
Must equal the frequency the CompiledScene was compiled at.
Non-empty frozenset of consumer components.
0 to MAX_DEPTH (5).
"scalar_transport" (complex scalar per row), "complex3_transport" (world-frame field vector per row) or "polarimetric_transport" (2 x 2 Jones matrix per row).
"discover".
"none", "jvp" or "vjp".
NonePositive limit on K. Exceeding it raises instead of truncating.
FixedTopologyRequest#
FixedTopologyRequest(sources, sinks, reference_frequency_hz, topology, response, ad_mode, world_motion="frozen_world", slot_count=1, frequency_offsets_hz=None)A replay request: evaluate known paths again for new endpoint positions.
A handle from prepare_fixed_topology. A raw PropagationTopology is accepted only for line-of-sight-only topologies.
'frozen_world'"frozen_world" requires all four scene versions to match the discovery. "fixed_winner_replay" also allows a changed geometry_version, replaying the same paths on moved geometry.
1Number of block-diagonal slots stacked in the endpoint batches. Values above 1 need a PreparedFixedTopology built with replicate_over_slots.
NoneHost tuple of frequency offsets. Column j is evaluated at reference_frequency_hz + frequency_offsets_hz[j]. Values must be finite, unique and resolvable on the native float32 grid; see native_frequency_resolution_hz.
sources, sinks, reference_frequency_hz, response and ad_mode mean the same as in PropagationRequest. Property frozen_topology returns the underlying PropagationTopology.
TimeVaryingRequest#
TimeVaryingRequest(sources, sinks, reference_frequency_hz, topology, times_s, response, ad_mode, world_motion="frozen_world")Replay one topology across S time slots in one call.
The per-slot prepared topology (from prepare_fixed_topology), not a replicated one. Channel replicates it over the slots.
Non-empty 1-D float64 tensor of S time labels. They label the slots; they do not drive any computation.
Source and sink batches hold S slots stacked slot-major: rows s * n to (s + 1) * n - 1 belong to slot s. Property slot_count returns S.
Functions#
evaluate#
evaluate(compiled_scene, request) -> PropagationEvaluationDiscovers the paths between all sources and sinks and evaluates them. Validates the scene, frequency, component, response and AD combination before any GPU work.
compiled = compile(scene, reference_frequency_hz=3.0e9)
result = consumer.evaluate(compiled, request)
print(result.paths.path_count) # K
print(result.paths.transport.coefficient) # (K,) complex64prepare_fixed_topology#
prepare_fixed_topology(topology) -> PreparedFixedTopologyGroups the rows of a discovered topology by (component, depth) for replay. It copies a few integers to the host and synchronizes once. Call it once per discovery and reuse the handle.
reevaluate#
reevaluate(compiled_scene, request) -> FixedTopologyEvaluationEvaluates the frozen rows of a FixedTopologyRequest without searching for paths. Rows can become invalid (row_valid is False) when a path is blocked at the new positions; new paths are never found. A changed topology_version, material_version or assignment_version raises; a changed geometry_version raises unless world_motion="fixed_winner_replay".
rediscovery_required#
rediscovery_required(compiled_scene, topology, *, revalidate_source=False) -> str | NoneReturns the name of the version domain that changed since the topology was discovered, or None. The default compares four host integers. revalidate_source=True also recomputes the versions from the source witwin.core world, which costs time proportional to the scene size. It cannot detect a new path that appeared under "fixed_winner_replay"; rediscover on a schedule for that.
replicate_over_slots#
replicate_over_slots(prepared, slot_count, *, source_count, sink_count) -> PreparedFixedTopologyRepeats a per-slot topology over slot_count block-diagonal slots, by index arithmetic only. source_count and sink_count are the endpoint counts per slot; they cannot be inferred because an endpoint without paths has no row. slot_count=1 returns prepared.
evaluate_time_varying#
evaluate_time_varying(compiled_scene, request) -> TimeVaryingEvaluationEvaluates all slots of a TimeVaryingRequest in one replay and returns (S, K) views. All slots share one CompiledScene, so structures must not move between slots; only endpoints move. See the time-varying channel tutorial.
capabilities#
capabilities() -> PropagationCapabilitiesReturns what this contract version supports. No GPU work.
caps = consumer.capabilities()
print(sorted(caps.components_for("scalar_transport"))) # ['diffraction', 'los', 'reflection', 'transmission']
print(caps.ad_modes_for_component("diffraction")) # frozenset({'none'})
print(caps.fixed_topology_components) # frozenset({'los', 'reflection'})native_frequency_resolution_hz#
native_frequency_resolution_hz(reference_frequency_hz) -> floatThe smallest frequency step the native float32 launch grid can resolve at this carrier, in hertz: 256.0 at 3 GHz, 8192.0 at 77 GHz. Offsets closer than this cannot be told apart. Non-finite or non-positive input raises ValueError.
Wideband replay refusals#
frequency_offsets_hz raises before any GPU work in these cases:
| Condition | Exception |
|---|---|
| The scene has dispersive materials | NotImplementedError |
| The scene has rough materials or phase screens | NotImplementedError |
| Offsets are empty, duplicate, non-finite or not resolvable | ValueError |
| Offsets are a tensor instead of a tuple | TypeError |
response="polarimetric_transport" | NotImplementedError |