Solvers and path types
The five propagation components Channel traces and the four solvers that combine them, with what each solver returns and supports.
Channel separates what a signal can do on its way to the receiver (the propagation components) from how the paths are found and combined (the solver). You pick both: a solver module, and a set of components in that solver's Config.
Propagation components#
Every Config has a components argument, a non-empty subset of these five names. The default is {"los", "reflection", "diffraction"}.
| Component | Physics | Notes |
|---|---|---|
"los" | Line of sight, free-space spreading wavelength / (4 * pi * d) | Blocked by any surface in between, unless the surface is part of a "transmission" path. |
"reflection" | Specular reflection with Fresnel coefficients of the hit material | Up to max_depth bounces, at most 5. |
"diffraction" | First-order edge diffraction with the uniform theory of diffraction (UTD) | Diffraction order is 1. max_diffraction_order accepts only 0 or 1. |
"transmission" | Penetration through layered thin-sheet walls | Opt-in. Each wall crossing counts toward max_depth, like a bounce. |
"scattering" | Diffuse scattering from rough surfaces (Kirchhoff model) | Opt-in. Needs materials with roughness_front or roughness_back. |
max_depth is the maximum number of interactions per path. max_depth=0 keeps only line of sight; max_depth=1 adds single reflections, single wall penetrations and edge diffraction.
Coupled reflection-diffraction paths#
With coupled_paths=True, the deterministic, path and BDPT solvers also trace paths that combine one reflection and one diffraction in either order (reflection then diffraction, diffraction then reflection) and double diffraction. Coupled paths need max_depth >= 2 and both "reflection" and "diffraction" in components. coupled_candidate_limit (default 1,000,000, the maximum) bounds the candidate search; exceeding it raises an error instead of dropping paths.
Component codes#
Path tables report a component_id per path:
| Code | Component |
|---|---|
| 0 | los |
| 1 | reflection |
| 2 | diffraction |
| 3 | reflection then diffraction |
| 4 | diffraction then reflection |
| 5 | transmission |
| 6 | scattering |
| 7 | double diffraction (coupled) |
The Path solver also reports per-interaction flags in interaction_type with the InteractionType flag enum: NONE=0, REFLECTION=1, DIFFRACTION=2, TRANSMISSION=4, SCATTERING=8.
The four solvers#
Each solver is a module with its own Config, Result and solve(scene, config, *, reference_frequency_hz). Configs are not interchangeable: passing one solver's Config to another raises.
| Module | Method | Main result | Use it for |
|---|---|---|---|
witwin.channel.deterministic | Exact path discovery, coherent sum of complex fields | path_gain, complex field, per-component power and fields, optional compact PathTable | Repeatable link values and radio maps, inverse problems |
witwin.channel.path | Exact path discovery per antenna pair | PathResult: complex coefficients a, delays tau, angles, interactions, CIR/CFR/taps, beamforming | Link-level channel models, arrays, delay spread |
witwin.channel.montecarlo.basic | Monte Carlo ray sampling, incoherent power | path_gain and per-component power and maps | Fast coverage estimates over large grids |
witwin.channel.montecarlo.bdpt | Bidirectional path tracing with multiple importance sampling (MIS) | path_gain, per-component power and maps, optional sample export | Coverage in scenes where unidirectional sampling misses paths |
What each solver supports, from witwin.channel.capabilities():
| Capability | deterministic | path | montecarlo.basic | montecarlo.bdpt |
|---|---|---|---|---|
| Complex path coefficients | yes | yes | no (power only) | yes, for coherent events |
| Polarization | yes | yes | no | yes |
| Antenna arrays | no | yes | no | no |
| Coupled reflection-diffraction | yes | yes | no | yes |
Gradients (ad_mode="jvp" / "vjp") | yes | yes | yes | see below |
| Receiver grids | yes, as a map | yes, each sample becomes one receiver | yes, as a map | yes, one grid per scene |
All five components are available in all four solvers. Scattering is not differentiable in any solver. In 0.5.0 the BDPT Config accepts ad_mode="jvp" and "vjp" and differentiates material, frequency and transmit-power parameters through the sampled paths, but capabilities() still reports BDPT as primal-only and refuses geometry gradients through its stochastic sampler. Treat BDPT gradients as experimental. See Differentiability.
Coherent and incoherent combination#
The deterministic solver adds the complex fields of all paths (coherent=True, the default), so path_gain shows interference fringes, as a real narrowband receiver would see. With coherent=False it adds powers instead. Monte Carlo Basic always adds powers. BDPT adds powers by default; coherent=True sums the line-of-sight, reflection and diffraction contributions as complex phasors per transmitter, receiver and component, and is refused together with "transmission" or "scattering".
Rough-surface scattering rows are incoherent power by default. In the deterministic solver, scattering_coherent=True sums them coherently, which is physical only for realization-coherent phase screens (see Materials).
Choosing a solver#
- Start with deterministic for a link or a radio map. Its results are exact for the traced paths and repeatable.
- Use path when you need individual paths, delays, angles, CIR/CFR or antenna arrays.
- Use Monte Carlo Basic for large grids where an incoherent power estimate is enough. Raise
samplesuntil the map stops changing. - Use BDPT when Monte Carlo Basic is noisy because the important paths are hard to hit from the transmitter side.
The radio map solver comparison runs all three radio-map solvers on one scene.
Failure behavior#
Channel never returns a partial or silently degraded result. An invalid configuration raises in the Config constructor (ValueError or RuntimeError). An unsupported combination, a capacity overflow, a missing native capability or a missing GPU raises during the solve, before any result is returned. Requested components are never dropped silently.