Weighted emitter selection¶
Use rfgen.planning when a generation job needs to draw one emitter family
and one class reproducibly from an ordered mixture. This is planning context:
it chooses a waveform owner, but does not construct that waveform, choose its
duration, place it, apply a channel, or generate IQ. Start with
Concepts / Scenes to decide how it relates to the
other planning records.
What the weights mean¶
Family and class weights are caller-supplied synthetic sampling priors. They control the relative frequency of choices in this generation job only. They are not measurements of RF occupancy, device prevalence, protocol traffic, or prevalence in a captured corpus. Use an evidence-backed modeling step outside this API when a job needs such a claim; then pass its chosen sampling mixture to this selector.
Quick start¶
from rfgen.planning import WeightedEmitterSelector
plan = WeightedEmitterSelector.resolve(
{
"seed": 1337,
"families": [
{"selector": "analog", "weight": 3.0, "classes": [{"name": "am"}, {"name": "fm"}]},
{"selector": "digital", "weight": 1.0, "classes": [{"name": "bpsk"}, {"name": "qpsk"}]},
],
}
)
WeightedEmitterSelector.write(plan, scene_id="capture-0001", root="output")
print(plan.draw.selector, plan.draw.class_name)
Models and artifact¶
SelectionConfig takes a uint64 seed and ordered families. A family has a
nonempty selector, nonnegative finite weight, and ordered classes. Each
class has a nonempty name and a nonnegative finite weight; omitting a
class or family weight means 1.0. YAML/list order is categorical order and
is retained in the artifact. A zero-weight family or class remains recorded
but is disabled for draws. Negative, nonfinite, or all-zero family/class
weights raise ValidationError with code: "selection_weights_invalid".
resolve draws a family with its normalized family weights, then a class with
that family’s normalized class weights, using numpy.random.Generator. The
returned SelectionPlan records the raw effective weights and each class’s
conditional probability; probabilities in each family sum to one within
1e-12. It writes one compact, versioned record at
artifacts/plans/<scene_id>/selection.json:
{"schema_version":1,"seed":1337,"families":[{"selector":"analog","weight":3.0,"classes":[{"name":"am","weight":1.0,"probability":0.5},{"name":"fm","weight":1.0,"probability":0.5}]}],"draw":{"selector":"analog","class_name":"am"}}
The stored draw must name an enabled family and class already present in the
stored ordered mixture. read validates this rather than drawing again.
write creates this path once and refuses to replace an existing file, so a
persisted selection remains immutable provenance for that scene ID.
For the statistical check and numerical operating limit, see
Weighted emitter selection validation.
Probability normalization¶
def normalize_probabilities(weights: tuple[float, ...]) -> tuple[float, ...]: ...
The public helper is implemented in src/rfgen/planning/selection.py. Its
input container must be a nonempty tuple. Every member must be a Python int
or float, but not bool, and must be finite and nonnegative; at least one
member must be positive. Every container or value violation, including an
empty or all-zero tuple, raises ValueError. A valid call returns a tuple of
Python floats in the same order whose sum is one within the implementation’s
1e-12 absolute check.
The implementation validates every member, divides all weights by the maximum
weight to avoid overflowing the sum, totals those scaled values with
math.fsum, divides
each scaled value by that total, and verifies the result.
The private compatibility name _normalized_probabilities is the identical
function object and therefore has the same behavior for valid downstream
calls; new code must import the public name.
This helper is Custom/library-composed: its normalization control flow is
custom and composes the Python standard library’s math.fsum. No established
project dependency exposes the same scalar-tuple API, explicit validation, and
overflow behavior required by this Core contract. Direct contract coverage is
in tests/unit/test_sampling_public_api.py; statistical and numerical evidence
is in the weighted-selection validation report.
Methods¶
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: ...
resolve(config)¶
Pass a SelectionConfig or an equivalent mapping. seed is an unsigned
64-bit integer. families is an ordered nonempty list; each family supplies a
nonempty selector, an optional finite nonnegative weight, and an ordered
nonempty classes list. Every class has a nonempty name and optional finite
nonnegative weight. Omitted weights are 1.0; zero-weight entries remain in
the plan but are not selectable.
Returns an immutable SelectionPlan containing the effective raw weights,
conditional class probabilities, and the selected enabled (selector, class_name) pair. Invalid configuration, negative/nonfinite weights, or an
all-zero family or class distribution raises the framework’s
rfgen.core.errors.ValidationError with
context["code"] == "selection_weights_invalid".
write(plan, *, scene_id, root=".")¶
Pass a SelectionPlan or equivalent mapping, a single safe path component as
scene_id, and the artifact-root directory. Returns the newly created
Path root/artifacts/plans/<scene_id>/selection.json.
Invalid v1 plan content or an unsafe scene ID raises ordinary ValueError.
If that exact canonical path already exists, write raises ordinary
FileExistsError and leaves its bytes unchanged; it has no overwrite mode.
Directory creation and filesystem-write failures propagate as ordinary
OSError subclasses.
read(path)¶
Pass the exact canonical artifact path
artifacts/plans/<scene_id>/selection.json. Returns the immutable validated
SelectionPlan without drawing again. A noncanonical path, unreadable or
invalid JSON payload, or a payload that violates the v1 artifact schema raises
ordinary ValueError; callers do not receive a structured
selection_weights_invalid error from this artifact-reading boundary.
ClassWeight, EmitterFamilyWeight, and SelectionConfig are the strict
input models. SelectionClass, SelectionFamily, SelectionDraw, and
SelectionPlan are immutable output/artifact models.