Path exchange

Backend-neutral propagation path records - PathRecord, interactions, provenance, and derivative payloads - shared by both RayD backends.

The path-exchange module describes one propagation path as plain Python data, independent of the backend that found it. Use it to hand reflection and diffraction paths to a channel model, a file, or another tool. Both backends expose the same API:

from rayd.drjit.path_exchange import PathRecord, reflection_path_record   # or rayd.torch.path_exchange

Records are frozen dataclasses and validate their contents when created. Values are Python floats and tuples, not arrays.

reflection_path_record#

reflection_path_record(
    global_primitive_ids: Iterable[int],
    *,
    segment_lengths: Iterable[float] | None = None,
    positions: Iterable[Sequence[float]] | None = None,
    normals: Iterable[Sequence[float]] | None = None,
    source_index: int = -1,
    receiver_index: int = -1,
    provenance: PathProvenance = PathProvenance.REFLECTION_TRACE,
    total_length: float | None = None,
    delay: float | None = None,
    aod: Sequence[float] | None = None,
    aoa: Sequence[float] | None = None,
    field: Sequence[complex] | None = None,
    power: float | None = None,
    differentiable_fields: PathDerivativeField = PathDerivativeField.NONE,
    derivative_mode: PathDerivativeMode = PathDerivativeMode.NONE,
    derivative: PathDerivative | None = None,
    interaction_derivatives: Iterable[PathInteractionDerivative] = (),
) -> PathRecord

Builds a valid reflection path with one REFLECTION interaction per primitive.

global_primitive_idsIterable[int]required

Scene-global reflector ids, in path order. Their count is the path order.

segment_lengthsIterable[float] | Nonedefault None

One length per reflection. When total_length is not given, it is set to their sum.

positionsIterable[Sequence[float]] | Nonedefault None

Reflection points, one 3-vector per reflection.

normalsIterable[Sequence[float]] | Nonedefault None

Surface normals, one 3-vector per reflection.

source_indexintdefault -1

Index of the transmitter in your own list; -1 means unknown.

receiver_indexintdefault -1

Index of the receiver; -1 means unknown.

provenancePathProvenancedefault REFLECTION_TRACE

Which operation produced the path.

total_lengthfloat | Nonedefault None

Total path length in scene units.

delayfloat | Nonedefault None

Propagation delay.

aodSequence[float] | Nonedefault None

Angle-of-departure direction, a 3-vector.

aoaSequence[float] | Nonedefault None

Angle-of-arrival direction, a 3-vector.

fieldSequence[complex] | Nonedefault None

Complex field, three components.

powerfloat | Nonedefault None

Path power, finite and non-negative.

differentiable_fieldsPathDerivativeFielddefault NONE

Fields that carry a derivative. Must be a subset of available_fields.

derivative_modePathDerivativeModedefault NONE

TANGENT (forward) or ADJOINT (reverse) when a derivative payload is attached.

derivativePathDerivative | Nonedefault None

Derivative of the path-level fields.

interaction_derivativesIterable[PathInteractionDerivative]default ()

Derivatives of interaction positions and normals, one per interaction.

Raises ValueError when positions, normals, or segment_lengths do not match the number of primitives, when a vector does not have three finite components, or when derivative fields and payloads are inconsistent.

from rayd.drjit.path_exchange import reflection_path_record, PathProvenance
 
rec = reflection_path_record(
    [0], segment_lengths=[2.23607], positions=[(0.5, 0.0, 0.0)], normals=[(0.0, 0.0, 1.0)],
    source_index=0, receiver_index=0, provenance=PathProvenance.REFLECTION_EPC,
    delay=2.23607 / 299792458.0)
print(rec.order, rec.total_length)        # 1 2.23607
print(int(rec.available_fields))          # 15: positions, normals, total length, delay

diffraction_path_record#

diffraction_path_record(
    global_edge_ids: Iterable[int],
    *,
    positions=None, normals=None, source_index=-1, receiver_index=-1,
    provenance: PathProvenance = PathProvenance.DIFFRACTION_DIRECT,
    total_length=None, delay=None, aod=None, aoa=None, field=None, power=None,
    differentiable_fields=PathDerivativeField.NONE, derivative_mode=PathDerivativeMode.NONE,
    derivative=None, interaction_derivatives=(),
) -> PathRecord

Builds a valid diffraction path with one DIFFRACTION interaction per scene-global edge id. The keyword arguments mean the same as in reflection_path_record; there is no segment_lengths.

Raises ValueError when positions or normals do not match the number of edges.

PathRecord#

PathRecord(
    valid: bool, order: int, source_index: int, receiver_index: int, provenance: PathProvenance,
    interactions: tuple[PathInteraction, ...] = (),
    total_length=None, delay=None, aod=None, aoa=None, field=None, power=None,
    fixed_winner: bool = True,
    differentiable_fields: PathDerivativeField = PathDerivativeField.NONE,
    derivative_mode: PathDerivativeMode = PathDerivativeMode.NONE,
    derivative: PathDerivative | None = None,
    interaction_derivatives: tuple[PathInteractionDerivative, ...] = (),
)

One path. Create it directly for imported paths; the two helpers fill it for you.

RuleError
order equals len(interactions)ValueError
source_index, receiver_index are -1 or non-negativeValueError
a valid path has a provenance other than UNKNOWNValueError
derivatives require fixed_winner=TrueValueError
differentiable_fields is a subset of available_fieldsValueError
interactions holds PathInteraction recordsTypeError

fixed_winner=True states that the derivatives follow RayD's fixed-winner contract.

Property available_fields -> PathDerivativeField: the fields that are present (interaction positions and normals when every interaction has them, and each non-None path field).

Method as_exchange_dict() -> dict: a JSON-friendly dict with enums as integers.

d = rec.as_exchange_dict()
print(d["provenance"], d["interactions"][0]["global_primitive_id"])   # 2 0

PathInteraction#

PathInteraction(kind: PathInteractionKind, global_primitive_id: int = -1, global_edge_id: int = -1,
                position: Vec3 | None = None, normal: Vec3 | None = None)

One event on the path. A REFLECTION needs a global_primitive_id, a DIFFRACTION needs a global_edge_id, and NONE carries neither.

PathDerivative, PathInteractionDerivative#

PathDerivative(total_length=None, delay=None, aod=None, aoa=None, field=None, power=None)
PathInteractionDerivative(position=None, normal=None)

Derivative payloads, in the direction given by derivative_mode. Each listed differentiable_fields entry must have a value; values must be finite.

Enumerations#

EnumMembers
PathInteractionKind (IntEnum)NONE 0, REFLECTION 1, DIFFRACTION 2
PathProvenance (IntEnum)UNKNOWN 0, REFLECTION_TRACE 1, REFLECTION_EPC 2, DIFFRACTION_DIRECT 3, DIFFRACTION_CHAIN 4, IMPORTED 5
PathDerivativeMode (IntEnum)NONE 0, TANGENT 1, ADJOINT 2
PathDerivativeField (IntFlag)NONE 0, INTERACTION_POSITION 1, INTERACTION_NORMAL 2, TOTAL_LENGTH 4, DELAY 8, AOD 16, AOA 32, COMPLEX_FIELD 64, POWER 128