Weighted emitter selection validation

Validated with documented limitations.

1. The component

rfgen.planning records one reproducible choice of an emitter family and one class before waveform generation. A categorical draw chooses one named outcome from a finite list using the list’s relative weights. It does not construct a waveform, determine duration, place a carrier, apply a channel, or generate IQ samples. Concepts / Scenes places this record among the other job-owned planning inputs.

The configured weights are caller-owned synthetic sampling priors. This validation does not treat them as measurements of RF occupancy, device prevalence, protocol traffic, or captured-corpus prevalence.

class WeightedEmitterSelector:
    @staticmethod
    def resolve(config: SelectionConfig | dict[str, object]) -> SelectionPlan: ...

    @staticmethod
    def write(
        plan: SelectionPlan | dict[str, object], *, scene_id: str,
        root: str | Path = "."
    ) -> Path: ...

    @staticmethod
    def read(path: str | Path) -> SelectionPlan: ...

Input or output

Contract

SelectionConfig

An ordered family/class mixture and an unsigned 64-bit seed; omitted weights are 1.0.

SelectionPlan

The raw ordered weights, per-family conditional class probabilities, and the selected enabled pair.

artifact path

artifacts/plans/<scene_id>/selection.json, with schema version 1.

from rfgen.planning import WeightedEmitterSelector

plan = WeightedEmitterSelector.resolve(
    {"seed": 1337, "families": [
        {"selector": "analog", "weight": 3.0,
         "classes": [{"name": "am", "weight": 2.0}, {"name": "fm"}]},
        {"selector": "digital", "weight": 1.0,
         "classes": [{"name": "bpsk"}, {"name": "qpsk"}]},
    ]}
)
path = WeightedEmitterSelector.write(plan, scene_id="capture-0001", root="output")
assert WeightedEmitterSelector.read(path) == plan

2. What we validated

This validation establishes three load-bearing claims. Each is supported in section 3.

  1. Seeded two-stage selection (§3.1): the configured family and class mixture determines a repeatable draw.

  2. Faithful selection artifact (§3.2): the stored record retains and validates the effective mixture and choice.

  3. Statistical frequency check (§3.3): a long categorical draw stream agrees with its configured distribution.

Section 4 gives the numerical and modeling boundaries.

3. Evidence per claim

3.1 Seeded two-stage selection

WeightedEmitterSelector.resolve first chooses a family from normalized family weights, then chooses a class from that family’s normalized class weights. It delegates both draws to NumPy Generator.choice, rather than implementing a random-selection algorithm. The focused acceptance suite confirms that the same configuration and seed return equal plans and that a zero-weight row remains visible but is not chosen.

The normalizer divides finite weights by the greatest weight before summing with math.fsum. This preserves representable ratios while avoiding an intermediate overflow. Equal family weights of 1e308 and equal class weights of 1e308 each produced probabilities 0.5, 0.5, a valid draw, and a successful artifact round trip.

3.2 Faithful selection artifact

tests/unit/test_weighted_selection.py::test_seeded_plan_retains_yaml_order_uses_uniform_omission_and_round_trips checks that family and class order survives, omitted weights become 1.0, each stored class probability equals its normalized stored raw class weight, and a write/read operation returns the same typed plan. A second write to the same canonical path raises FileExistsError and preserves the existing bytes. The canonical outer record has exactly schema_version, seed, families, and draw.

The same focused suite rejects a stored probability that disagrees with its raw weight, a zero-total stored class distribution, a draw naming an absent or disabled outcome, a noncanonical artifact location, and an unsafe scene ID. These checks establish record integrity, not RF realism.

3.3 Statistical frequency check

For the documented mixture, the expected final outcome probabilities are 0.5, 0.25, 0.125, and 0.125. An independent 100,000-draw NumPy stream seeded with 1337 observed 0.50047, 0.24941, 0.12608, and 0.12404. A standard error is the expected sampling variation of a measured proportion under repeated draws. The signed deviations were 0.297, -0.431, 1.033, and -0.918 standard errors, so every outcome is inside the stated five-standard-error acceptance bound.

4. Limits and what is not validated

  • The caller supplies the mixture. This component does not establish that its weights represent measured RF occupancy, device prevalence, protocol traffic, or a captured corpus.

  • Probabilities use IEEE 754 binary64 floating point. A positive weight whose ratio to the greatest weight underflows that representation is stored with probability zero and cannot be sampled by the NumPy backend; the raw weight remains in the artifact. Inputs should use ratios that yield representable probabilities when every positive outcome must remain selectable.

  • Device identity, event count, timing, duration, placement, waveform generation, propagation, labels, and receiver behavior are outside this component.

5. References

  1. NumPy Generator.choice documentation. PyPI distribution: numpy; installed version: 2.4.1. It supplies the categorical draws and the independent frequency check.

  2. Pydantic documentation. PyPI distribution: pydantic; installed version: 2.13.3. It supplies the strict immutable schemas for public input and persisted records.

  3. Weighted emitter selection API. Public input, artifact, ordering, and structured-error contract.