Skip to content

[FEATURE] compute_received_fields / compute_received_power #427

@rwydaegh

Description

@rwydaegh

Terms

  • Checked the existing issues to see if my suggestion has not already been suggested;

Description

compute_paths gives you geometry. Vertices, object indices, a mask. But no power, no fields. The EM module has fresnel_coefficients, sp_directions, Dipole.fields, the material database, etc. but there's nothing that strings them together for you.

The only place the full pipeline actually works is deepmimo.py export (lines 410-494), inside a plugin most users won't find. #355 tried to fix accuracy there and got stuck with ~3-16 degrees of phase error on multi-bounce.

Without an easy way to go from geometry to power, the tutorials end up doing 0.5 ** num_bounces / path_length**2 (see smoothing.ipynb).

Jerome has mentioned that EM/radio-propagation features are a known gap, since his thesis focused on the geometry side of ray paths. He's expressed interest in contributions here.

I'm proposing a small differt/em/_pipeline.py module with a few functions, exposed at differt.em:

@eqx.filter_jit
def compute_received_fields(
    paths: Paths,
    mesh: TriangleMesh,
    frequency: Float[ArrayLike, ""],
    *,
    antenna_tx: BaseAntenna | None = None,
    antenna_rx: BaseAntenna | None = None,
    polarization: Float[ArrayLike, "3"] = jnp.array([0.0, 0.0, 1.0]),
) -> tuple[Complex[Array, "*batch 3"], Float[Array, "*batch"]]:
    """Per-path complex E-field vector and propagation delay."""

@eqx.filter_jit
def compute_received_power(
    paths: Paths,
    mesh: TriangleMesh,
    frequency: Float[ArrayLike, ""],
    *,
    dB: bool = False,
    coherent: bool = True,
    **kwargs,
) -> Float[Array, " *reduced_batch"]:
    """Total received power, summed over paths."""

@eqx.filter_jit
def compute_cir(
    paths: Paths,
    mesh: TriangleMesh,
    frequency: Float[ArrayLike, ""],
    **kwargs,
) -> tuple[Complex[Array, " *reduced_batch"], Float[Array, " *reduced_batch"]]:
    """Channel impulse response: (complex amplitude, delay) per path."""

These take Paths + TriangleMesh instead of TriangleScene so they work at any abstraction level.

Internally this is mostly pulling the logic out of deepmimo.py:410-494 into a standalone function: compute segment directions, look up materials, get Fresnel coefficients per bounce, project into s/p basis, apply coefficients, spreading factor, phase shift. It should dispatch on interaction_types so once #429 (diffraction_coefficients) is done, diffraction just plugs in. For now it only needs to handle REFLECTION.

When paths.mask is float-valued (smoothing mode), fields get multiplied by confidence rather than boolean-indexed, so jax.grad(compute_received_power)(...) works out of the box.

Once this exists, deepmimo.export() should just call it internally.

For validation I'd compare LOS against analytic fspl(), single reflection against the manual example in the reflection_coefficients docstring (which gets Brewster angle right), and multi-reflection against Sionna on a simple scene.

This would also make #430 (compute_coverage_map) and #431 (transition_matrices) possible, since both need a way to get power from paths. It supersedes #355, which tried to fix EM in the DeepMIMO export and got stuck. #206 and #202 also need this before they can be closed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestnice-to-haveA nice to have feature (or else)

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions