Duration policy¶
Use rfgen.planning when a generation job needs to resolve a standalone event
duration decision. In the established manual event-plan workflow, first create
unresolved events, resolve one duration for each event, then pass the resulting
sample counts and containment values to EventPlanner.resolve. Start with
Concepts / Scenes: duration is one planning decision
alongside selected waveform class and event-start provenance. This API neither
schedules starts, creates a waveform, nor composes receiver IQ.
Quick start¶
from rfgen.planning import DurationPolicyResolver
plan = DurationPolicyResolver.resolve(
{
"classes": {"qpsk": {"kind": "fixed", "value_s": 0.020, "seed": 7}},
"scene": {"kind": "uniform", "min_s": 0.005, "max_s": 0.050, "seed": 8},
},
class_name="qpsk",
family="digital",
role="uplink",
sample_rate_hz=20_000_000,
capture_samples=2_000_000,
)
DurationPolicyResolver.write(plan, scene_id="capture-0001", root="output")
Configuration, policy shapes, and result¶
DurationPolicyConfig is a strict, frozen mapping with exactly these fields:
Field |
Type and default |
Meaning |
|---|---|---|
|
|
Optional policy keyed by emitted class name. |
|
|
Optional policy keyed by emitter family. |
|
|
Optional policy keyed by job-owned role name. |
|
|
Fallback policy when no more-specific key matches. |
Map keys must be non-empty strings. Unknown configuration fields, coercions
such as strings for numeric values, and unknown fields inside a policy are
rejected. Resolution uses the first available matching policy in this order:
class, family, role, scene. The returned DurationPlan.issuer records which
level supplied it. Passing a non-None class, family, or role argument with an
empty or non-string value is invalid.
Each DurationPolicy has a uint64 seed and one of these strict shapes:
Kind |
Required fields |
Draw |
|---|---|---|
|
|
That positive finite duration. |
|
|
NumPy |
|
|
|
All duration values and bounds must be finite and strictly positive. A policy
must contain only the fields for its declared kind: for example, fixed
forbids min_s, max_s, mu, and sigma; uniform forbids value_s,
mu, and sigma; and lognormal forbids value_s. Bounds are inclusive.
These values are caller-supplied synthetic planning priors. They do not establish transmitter, protocol, corpus-traffic, or transmitter-on-time realism. Calibrate the policy values against appropriate evidence before using them to make any of those claims.
Seconds become samples with decimal round-half-even arithmetic. A lognormal
draw that has no in-bounds value in 64 attempts raises PlacementError with
{code: "duration_draw_failed", attempts: 64}; it never silently substitutes
an endpoint.
Numerical safe envelope¶
For a sample_rate_hz capture, the requested duration must be strictly greater
than 0.5 / sample_rate_hz seconds to round to at least one sample. An exact
half-sample tie rounds to zero under round-half-even, as do smaller positive
durations. resolve rejects that result with its existing
duration_policy_invalid error for requested_duration_s; it does not round
up or create a one-sample event.
The default is strict containment. If the resolved sample count exceeds
capture_samples, resolve raises PlacementError with
{code: "duration_not_contained", duration_samples, capture_samples}. Pass
allow_clipping=True only when the job deliberately permits truncation. The
returned plan then says contained: false, records the capture-sized realized
count, and records the same count as clipped_duration_samples.
DurationPlan record¶
resolve returns an immutable, strict version-1 DurationPlan. It is the
durable record of one resolved duration decision, not an event schedule or a
waveform request.
Field |
Type |
Invariant and meaning |
|---|---|---|
|
literal integer |
Version of this artifact schema. |
|
|
Kind of the selected policy. |
|
positive finite seconds |
Declared inclusive bounds; |
|
uint64 |
Seed of the selected policy. |
|
positive finite seconds |
Draw before conversion; it lies within the recorded bounds. |
|
positive integer samples |
Requested sample count when contained; capture-sized count when clipped. |
|
|
Scope that supplied the policy. |
|
boolean |
Whether the requested sample count fitted the capture. |
|
positive integer or |
|
write creates the immutable artifact
artifacts/plans/<scene_id>/durations.json; it refuses to replace one. Its
exact schema is:
{"schema_version":1,"policy_kind":"fixed","min_s":0.02,"max_s":0.02,"seed":7,"requested_duration_s":0.02,"realized_duration_samples":400000,"issuer":"class","contained":true,"clipped_duration_samples":null}
Resolver operations¶
class DurationPolicyResolver:
@staticmethod
def resolve(config: DurationPolicyConfig | dict[str, object], *, class_name: str | None, family: str | None, role: str | None, sample_rate_hz: float, capture_samples: int, allow_clipping: bool = False) -> DurationPlan: ...
@staticmethod
def write(plan: DurationPlan | dict[str, object], *, scene_id: str, root: str | Path = ".") -> Path: ...
@staticmethod
def read(path: str | Path) -> DurationPlan: ...
resolve¶
resolve validates config, chooses the most-specific matching policy, draws
the requested seconds deterministically from its seed, applies decimal
round-half-even conversion, and checks it against the capture boundary.
Input |
Required |
Meaning |
|---|---|---|
|
yes |
A |
|
yes (may each be |
Lookup keys used in precedence order. They do not create an event. |
|
yes |
Finite positive sample rate in samples/s. |
|
yes |
Positive integer receiver-capture length in samples. |
|
no; |
Whether an overlong request may become an explicit clipped plan. |
It returns DurationPlan. Invalid configuration, a non-boolean
allow_clipping, invalid capture_samples, or a duration that rounds to zero
samples raise ValidationError with code: "duration_policy_invalid".
Invalid lookup keys or sample_rate_hz raise ValueError. An overlong request
with clipping disabled raises PlacementError with
code: "duration_not_contained"; a bounded lognormal that cannot draw in
range raises PlacementError with code: "duration_draw_failed".
write and read¶
write accepts a DurationPlan or matching mapping, validates it, and writes
exactly one JSON object to artifacts/plans/<scene_id>/durations.json under
root. scene_id must be one safe non-empty path component. The write is
exclusive: an existing artifact raises FileExistsError instead of being
replaced. It returns the written Path.
read accepts only that canonical durations.json path. It reads and strictly
validates the exact version-1 field set and all DurationPlan invariants; it
does not redraw the policy. A wrong canonical path, malformed JSON, missing or
extra fields, or incompatible record raises ValueError.
Validation and limits¶
Duration policy validation records the tested resolution, sampling, containment, and immutable-artifact claims, plus the limits on interpreting caller-supplied synthetic priors.