Skip to content

Python Source API — NCO / LO / AWGN

Three signal-source classes in the doppler.source module:

Class Output Use when
LO CF32 complex phasors via 2¹⁶-entry sin/cos LUT Generate IQ tones, FM signals
NCO uint32 raw phase accumulator Drive polyphase clock, generate carries
AWGN CF32 complex Gaussian noise Noise injection, SNR testing, Monte Carlo

Source: src/doppler/source/__init__.py


LO — complex phasor generator

96 dBc SFDR from 16-bit phase truncation into the 65 536-entry LUT.

from doppler.source import LO
import numpy as np

lo = LO(0.25)              # normalised frequency: 0.25 → Fs/4

# Batch generate
iq = lo.steps(1024)        # complex64, length 1024
print(iq[:4])
# [ 1.+0.j  0.+1.j -1.+0.j  0.-1.j ]

# One sample (LO is block-oriented; take the first of a length-1 batch)
s = lo.steps(1)[0]

# FM control port — per-sample frequency deviation
ctrl = (0.002 * np.sin(2 * np.pi * 0.01 * np.arange(1024))).astype(np.float32)
iq_fm = lo.steps_ctrl(ctrl)

# Retune without resetting phase
lo.norm_freq = 0.1

Phase continuity

lo = LO(0.25)
a = lo.steps(512)
b = lo.steps(512)   # seamlessly continues from sample 512

NCO — raw phase accumulator

Useful for generating sample-clock events (overflow carries) that drive a polyphase resampler.

from doppler.source import NCO
import numpy as np

nco = NCO(0.25)

# Raw 32-bit phase values
ph = nco.steps_u32(16)

# Overflow carry — 1 at each wrap (every 4 samples for 0.25)
carry = nco.steps_u32_ovf(16)
# carry: [0, 0, 0, 1, 0, 0, 0, 1, ...]

# Scaled to [0, nmax) — fixed-point multiply, no division
nco2 = NCO(0.25, nmax=1000)
scaled = nco2.steps_u32_scaled(16)   # values in [0, 1000)

LO

Create an LO instance. Allocates state, sets phase to 0, and derives phase_inc from norm_freq. Initialises the shared 65536-entry float LUT on the first call (single-threaded concern: call lo_create() before spawning threads that share LO instances).

Parameters:

Name Type Description Default
norm_freq float

Normalised frequency in cycles per sample. Any real value; only the fractional part matters.

0.0

Examples:

>>> from doppler.source import LO
>>> lo = LO(norm_freq=0.25)
>>> lo.phase_inc
1073741824

norm_freq property writable

norm_freq: float

Normalised frequency (read/write). Setting norm_freq recomputes phase_inc = floor(frac(v) × 2^32) and takes effect on the next lo_steps call; phase is NOT reset.

phase property writable

phase: int

Current phase accumulator value (read/write). Returns the current integer phase in [0, 2^32). Writing overrides the accumulator directly for phase-coherent frequency switching.

phase_inc property

phase_inc: int

Per-sample phase increment (read-only). Derived from norm_freq as floor(frac(norm_freq) × 2^32). A freq of 0.25 gives phase_inc = 1073741824 (0x40000000).

reset

reset() -> None

Zero the phase accumulator. Sets phase to 0 so the next lo_steps call starts at angle 0 (1+0j). norm_freq and phase_inc are unchanged.

Examples:

>>> from doppler.source import LO
>>> lo = LO(0.25)
>>> _ = lo.steps(2)
>>> lo.phase
2147483648
>>> lo.reset()
>>> lo.phase
0
>>> lo.norm_freq
0.25

steps

steps(
    count: int = 1, out: NDArray[complex64] | None = None
) -> NDArray[np.complex64]

Generate n CF32 phasors at the current norm_freq. Each sample is cos(θ) + j·sin(θ) where θ is the phase BEFORE the accumulator is advanced, giving a unit-magnitude complex sinusoid via the 65536-entry LUT. SFDR ≈ 96 dBc. Returns n.

Returns:

Type Description
NDArray[complex64]

min(n, max_out) samples.

Examples:

>>> from doppler.source import LO
>>> lo = LO(0.25)
>>> out = lo.steps(4)
>>> out.dtype
dtype('complex64')
>>> out.shape
(4,)
>>> [round(float(abs(c)), 4) for c in out]
[1.0, 1.0, 1.0, 1.0]

steps_max_out

steps_max_out() -> int

Maximum samples per call (determines pre-allocated buffer size).

Returns:

Type Description
int

Output.

steps_ctrl

steps_ctrl(
    ctrl: NDArray[float32],
    out: NDArray[complex64] | None = None,
) -> NDArray[np.complex64]

Generate CF32 phasors with per-sample FM deviation. For each sample i, ctrl[i]'s fractional part is converted to a delta phase-increment (delta = floor(frac(ctrl[i]) × 2^32)) that is added on top of the base phase_inc for that one step only. The base norm_freq and phase_inc are NOT modified; the deviation is transient per sample, making this the natural API for FM synthesis and frequency-hopping. Output length equals ctrl_len. Returns ctrl_len.

Parameters:

Name Type Description Default
ctrl NDArray[float32]

Float32 array of per-sample normalised-frequency deviations. Only the fractional part of each element contributes.

required

Returns:

Type Description
NDArray[complex64]

min(ctrl_len, max_out) samples.

Examples:

>>> import numpy as np
>>> from doppler.source import LO
>>> lo = LO(0.25)
>>> ctrl = np.zeros(4, dtype=np.float32)
>>> out = lo.steps_ctrl(ctrl)
>>> out.dtype
dtype('complex64')
>>> out.shape
(4,)
>>> [round(float(abs(c)), 4) for c in out]
[1.0, 1.0, 1.0, 1.0]

steps_ctrl_max_out

steps_ctrl_max_out() -> int

Largest number of samples steps_ctrl() can return in the current state.

Size an out= buffer with this before calling steps_ctrl(), or use it to allocate one up front. The bound is this object's own: what it depends on is a property of the algorithm, so a header block on steps_ctrl_max_out() replaces this text.

Returns:

Type Description
int

Upper bound on the output length; the actual call may return fewer.

state_bytes

state_bytes() -> int

Size in bytes of this object's serialized state.

The exact length get_state returns and set_state requires. It depends on how the object was constructed (state arrays are sized at construction), so read it from the instance rather than assuming a constant.

Raises RuntimeError if the LO has already been destroyed.

Returns:

Type Description
int

Byte length of one serialized state blob.

get_state

get_state() -> bytes

Serialize this object's mutable state to bytes.

Captures exactly the state that evolves as the object runs, so a blob taken now and restored later resumes from this point. Construction parameters are not included: restore into an object built the same way.

The blob is opaque and always state_bytes() long. Its layout is an implementation detail of the C core and is not a stable format across builds.

Raises RuntimeError if the LO has already been destroyed.

Returns:

Type Description
bytes

Opaque snapshot, state_bytes() bytes long.

set_state

set_state(blob: bytes) -> None

Restore mutable state from a get_state() blob.

Overwrites the live state in place; the object keeps the parameters it was constructed with. Length is validated against state_bytes() before the blob is handed to the C core, and the core may reject it as well.

Raises TypeError if blob is not bytes, ValueError if its length differs from state_bytes() or the core rejects it, and RuntimeError if the LO has already been destroyed.

Parameters:

Name Type Description Default
blob bytes

A get_state() blob from this type, exactly state_bytes() long.

required

destroy

destroy() -> None

Release the underlying C resources immediately.

Ordinarily unnecessary: the resources are freed when the object is garbage-collected. Call this to release them at a definite point instead, or use the object as a context manager, which calls it on exit.

Idempotent: calling it again on an already-released object does nothing. Every other method raises RuntimeError once it has run.

__enter__

__enter__() -> LO

Enter a context manager, returning this object.

Lets a LO be used in a with statement so its C resources are released deterministically on exit rather than at collection time.

Returns:

Type Description
LO

This same object, not a copy.

__exit__

__exit__(
    exc_type: object | None = ...,
    exc: object | None = ...,
    tb: object | None = ...,
) -> None

Exit a context manager, releasing the LO.

Equivalent to calling destroy(). Returns None, so an exception raised inside the with body propagates normally; this never suppresses one.

Parameters:

Name Type Description Default
exc_type object | None

Exception class, or None. Ignored.

...
exc object | None

Exception instance, or None. Ignored.

...
tb object | None

Traceback object, or None. Ignored.

...

NCO

Create an NCO instance. Allocates and initialises the phase accumulator to zero, converts norm_freq to the integer phase_inc = floor(frac(norm_freq) × 2^32), and stores nmax for scaled output. The NCO is immediately ready to call nco_steps_u32 / nco_steps_u32_scaled / nco_steps_u32_ovf.

Parameters:

Name Type Description Default
norm_freq float

Normalised frequency in cycles per sample. Any real value; only the fractional part matters. Negative values fold correctly (−0.25 → 3×2^30).

0.0
nmax int

Wrap target for nco_steps_u32_scaled. Pass 0 to return the raw 32-bit accumulator.

0

Examples:

>>> from doppler.source import NCO
>>> nco = NCO(norm_freq=0.25, nmax=0)
>>> nco.phase_inc
1073741824

norm_freq property writable

norm_freq: float

Normalised frequency (read/write). Setting norm_freq recomputes phase_inc = floor(frac(v) × 2^32) and takes effect on the next nco_steps_* call; phase is NOT reset.

phase property writable

phase: int

Current phase accumulator value (read/write). Reading returns the current integer phase in [0, 2^32). Writing overrides the accumulator directly, allowing arbitrary phase offsets without re-creating the NCO.

phase_inc property

phase_inc: int

Per-sample phase increment (read-only). Derived from norm_freq as floor(frac(norm_freq) × 2^32). Updated automatically whenever norm_freq is written. A freq of 0.25 gives phase_inc = 1073741824 (0x40000000).

reset

reset() -> None

Zero the phase accumulator. Sets phase to 0 so the next nco_steps_u32 call starts from the beginning of the cycle. norm_freq, phase_inc, and nmax are unchanged; the NCO is ready to generate samples again immediately.

Examples:

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 0)
>>> _ = nco.steps_u32(2)
>>> nco.phase
2147483648
>>> nco.reset()
>>> nco.phase
0
>>> nco.norm_freq
0.25

steps_u32

steps_u32(
    count: int = 1, out: NDArray[uint32] | None = None
) -> NDArray[np.uint32]

Advance n samples; write raw uint32 accumulator values. Each element is the phase value BEFORE the increment fires, so out[0] is the phase at the moment of the call. The accumulator wraps silently at 2^32, giving the full-resolution integer ramp that the scaled and carry variants derive from. Returns n.

Returns:

Type Description
NDArray[uint32]

min(n, max_out) samples.

Examples:

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 0)
>>> out = nco.steps_u32(4)
>>> out.dtype
dtype('uint32')
>>> out.tolist()
[0, 1073741824, 2147483648, 3221225472]

steps_u32_max_out

steps_u32_max_out() -> int

Maximum samples per call (determines pre-allocated buffer size).

The Python extension pre-allocates output buffers of this size at

create time. Requesting more samples per call is undefined behaviour.

Returns:

Type Description
int

Output.

steps_u32_scaled

steps_u32_scaled(
    count: int = 1, out: NDArray[uint32] | None = None
) -> NDArray[np.uint32]

Advance n samples; values scaled to [0, nmax). Uses the branchless fixed-point identity out[i] = (uint64_t)phase * nmax >> 32 to map the full accumulator range uniformly onto [0, nmax) without a modulo operation. When nmax == 0 falls back to the raw accumulator (identical to nco_steps_u32). Useful for polyphase filter bank indexing and direct LUT addressing. Returns n.

Returns:

Type Description
NDArray[uint32]

min(n, max_out) samples.

Examples:

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 4)
>>> out = nco.steps_u32_scaled(4)
>>> out.dtype
dtype('uint32')
>>> out.tolist()
[0, 1, 2, 3]

steps_u32_scaled_max_out

steps_u32_scaled_max_out() -> int

Largest number of samples steps_u32_scaled() can return in the current state.

Size an out= buffer with this before calling steps_u32_scaled(), or use it to allocate one up front. The bound is this object's own: what it depends on is a property of the algorithm, so a header block on steps_u32_scaled_max_out() replaces this text.

Returns:

Type Description
int

Upper bound on the output length; the actual call may return fewer.

steps_u32_ovf

steps_u32_ovf(
    count: int = 1,
) -> tuple[NDArray[np.uint32], NDArray[np.uint8]]

Advance n samples; write raw phase values and per-sample carry. Identical to nco_steps_u32 for the phase array, but simultaneously fills a parallel uint8 carry buffer: out1[i] is 1 if the add that produced out[i]'s post-increment phase wrapped past 2^32, else 0. The carry marks the exact boundary of one input period and is the primitive for polyphase sample-clock and rational resampling engines. Returns n.

Returns:

Type Description
tuple[NDArray[uint32], NDArray[uint8]]

min(n, max_out) samples.

Examples:

>>> from doppler.source import NCO
>>> nco = NCO(0.5, 0)
>>> ph, carry = nco.steps_u32_ovf(4)
>>> ph.tolist()
[0, 2147483648, 0, 2147483648]
>>> carry.tolist()
[0, 1, 0, 1]
>>> carry.dtype
dtype('uint8')

steps_u32_ctrl

steps_u32_ctrl(
    ctrl: NDArray[float32],
    out: NDArray[uint32] | None = None,
) -> NDArray[np.uint32]

Advance ctrl_len samples; raw phase, with a per-sample control offset added on top of the fixed phase_inc (not persisted).

The NCO control port for a tracking loop: ctrl is a per-sample frequency control in normalised cycles/sample, added to the centre increment phase_inc for that step only. phase_inc / norm_freq are NEVER modified by this call -- only the running phase advances, by phase_inc + ctrl_inc each sample -- so a loop filter can drive the NCO with its full per-sample output (integrator + proportional term) without the caller ever touching the NCO's own configured rate. Mirrors lo_step_ctrl/lo_steps_ctrl (native/inc/lo/lo_core.h), which does this for the CF32 phasor output; this is the same control-port pattern for NCO's raw phase output. With every ctrl[i] == 0 this is bit-identical to nco_steps_u32(). Returns ctrl_len.

Python's out= keyword writes directly into a caller-supplied buffer instead of allocating a fresh one -- essential for driving this from a hot per-epoch tracking loop with no per-call allocation (fill ctrl in place, reuse the same out buffer every call). That buffer must be sized to steps_u32_ctrl_max_out(), NOT just len(ctrl) -- the returned view is still correctly sliced to len(ctrl) regardless of the buffer's actual size.

Parameters:

Name Type Description Default
ctrl NDArray[float32]

Float32 array of per-sample normalised-frequency control offsets, any sign (the fractional cycle is taken, so it wraps correctly).

required

Returns:

Type Description
NDArray[uint32]

min(ctrl_len, max_out) samples.

Examples:

>>> from doppler.source import NCO
>>> import numpy as np
>>> nco = NCO(norm_freq=0.0, nmax=0)
>>> ctrl = np.full(4, 0.25, dtype=np.float32)
>>> out = nco.steps_u32_ctrl(ctrl)
>>> out.tolist()
[0, 1073741824, 2147483648, 3221225472]
>>> nco.norm_freq
0.0

steps_u32_ctrl_max_out

steps_u32_ctrl_max_out() -> int

Largest number of samples steps_u32_ctrl() can return in the current state.

Size an out= buffer with this before calling steps_u32_ctrl(), or use it to allocate one up front. The bound is this object's own: what it depends on is a property of the algorithm, so a header block on steps_u32_ctrl_max_out() replaces this text.

Returns:

Type Description
int

Upper bound on the output length; the actual call may return fewer.

steps_u32_scaled_ctrl

steps_u32_scaled_ctrl(
    ctrl: NDArray[float32],
    out: NDArray[uint32] | None = None,
) -> NDArray[np.uint32]

Advance ctrl_len samples; values scaled to [0, nmax), with a per-sample control offset added on top of phase_inc.

The nco_steps_u32_scaled output mapping (nmax=0 falls back to the raw accumulator) driven by the nco_steps_u32_ctrl control port -- every stepper has a matching control-input counterpart, so a tracking loop can drive LUT-indexed output (nmax = table length) exactly as it would raw phase output, without ever touching phase_inc/norm_freq. With every ctrl[i] == 0 this is bit-identical to nco_steps_u32_scaled(). Returns ctrl_len.

Parameters:

Name Type Description Default
ctrl NDArray[float32]

Float32 array of per-sample normalised-frequency control offsets, any sign (the fractional cycle is taken, so it wraps correctly).

required

Returns:

Type Description
NDArray[uint32]

min(ctrl_len, max_out) samples.

Examples:

>>> from doppler.source import NCO
>>> import numpy as np
>>> nco = NCO(norm_freq=0.0, nmax=4)
>>> ctrl = np.full(4, 0.25, dtype=np.float32)
>>> out = nco.steps_u32_scaled_ctrl(ctrl)
>>> out.tolist()
[0, 1, 2, 3]

steps_u32_scaled_ctrl_max_out

steps_u32_scaled_ctrl_max_out() -> int

Largest number of samples steps_u32_scaled_ctrl() can return in the current state.

Size an out= buffer with this before calling steps_u32_scaled_ctrl(), or use it to allocate one up front. The bound is this object's own: what it depends on is a property of the algorithm, so a header block on steps_u32_scaled_ctrl_max_out() replaces this text.

Returns:

Type Description
int

Upper bound on the output length; the actual call may return fewer.

steps_u32_ovf_ctrl

steps_u32_ovf_ctrl(
    ctrl: NDArray[float32],
) -> tuple[NDArray[np.uint32], NDArray[np.uint8]]

Advance ctrl_len samples; raw phase + per-sample carry, with a per-sample control offset added on top of phase_inc.

The nco_steps_u32_ovf output mapping (raw phase plus a carry flag marking each sample whose advance wrapped past 2^32) driven by the nco_steps_u32_ctrl control port -- every stepper has a matching control-input counterpart. The carry reflects THIS sample's true advance (phase_inc + ctrl_inc, added as a single 64-bit sum so a wrap is never missed even when the control offset itself is large), not just phase_inc alone -- needed by any consumer (e.g. a coupled carrier/code tracker) that must detect a period boundary while the rate is being actively steered. With every ctrl[i] == 0 this is bit-identical to nco_steps_u32_ovf(). Returns ctrl_len.

Parameters:

Name Type Description Default
ctrl NDArray[float32]

Float32 array of per-sample normalised-frequency control offsets, any sign (the fractional cycle is taken, so it wraps correctly).

required

Returns:

Type Description
tuple[NDArray[uint32], NDArray[uint8]]

min(ctrl_len, max_out) samples.

Warnings

The carry is wrong for a negative control -- see nco_step_u32_ovf_ctrl.

Examples:

>>> from doppler.source import NCO
>>> import numpy as np
>>> nco = NCO(norm_freq=0.25, nmax=0)
>>> ctrl = np.zeros(4, dtype=np.float32)
>>> ph, carry = nco.steps_u32_ovf_ctrl(ctrl)
>>> ph.tolist()
[0, 1073741824, 2147483648, 3221225472]
>>> carry.tolist()
[0, 0, 0, 1]

state_bytes

state_bytes() -> int

Size in bytes of this object's serialized state.

The exact length get_state returns and set_state requires. It depends on how the object was constructed (state arrays are sized at construction), so read it from the instance rather than assuming a constant.

Raises RuntimeError if the NCO has already been destroyed.

Returns:

Type Description
int

Byte length of one serialized state blob.

get_state

get_state() -> bytes

Serialize this object's mutable state to bytes.

Captures exactly the state that evolves as the object runs, so a blob taken now and restored later resumes from this point. Construction parameters are not included: restore into an object built the same way.

The blob is opaque and always state_bytes() long. Its layout is an implementation detail of the C core and is not a stable format across builds.

Raises RuntimeError if the NCO has already been destroyed.

Returns:

Type Description
bytes

Opaque snapshot, state_bytes() bytes long.

set_state

set_state(blob: bytes) -> None

Restore mutable state from a get_state() blob.

Overwrites the live state in place; the object keeps the parameters it was constructed with. Length is validated against state_bytes() before the blob is handed to the C core, and the core may reject it as well.

Raises TypeError if blob is not bytes, ValueError if its length differs from state_bytes() or the core rejects it, and RuntimeError if the NCO has already been destroyed.

Parameters:

Name Type Description Default
blob bytes

A get_state() blob from this type, exactly state_bytes() long.

required

destroy

destroy() -> None

Release the underlying C resources immediately.

Ordinarily unnecessary: the resources are freed when the object is garbage-collected. Call this to release them at a definite point instead, or use the object as a context manager, which calls it on exit.

Idempotent: calling it again on an already-released object does nothing. Every other method raises RuntimeError once it has run.

__enter__

__enter__() -> NCO

Enter a context manager, returning this object.

Lets a NCO be used in a with statement so its C resources are released deterministically on exit rather than at collection time.

Returns:

Type Description
NCO

This same object, not a copy.

__exit__

__exit__(
    exc_type: object | None = ...,
    exc: object | None = ...,
    tb: object | None = ...,
) -> None

Exit a context manager, releasing the NCO.

Equivalent to calling destroy(). Returns None, so an exception raised inside the with body propagates normally; this never suppresses one.

Parameters:

Name Type Description Default
exc_type object | None

Exception class, or None. Ignored.

...
exc object | None

Exception instance, or None. Ignored.

...
tb object | None

Traceback object, or None. Ignored.

...

AWGN — Additive White Gaussian Noise

xoshiro256++ RNG + Box-Muller transform. Per-component std dev = amplitude. AVX-512 path runs 8 independent streams in parallel (~525 MSa/s).

from doppler.source import AWGN
import numpy as np

g = AWGN(seed=42, amplitude=1.0)
noise = g.generate(1024)    # complex64, length 1024

# Amplitude can be changed without disturbing the RNG state
g.amplitude = 0.5

# Deterministic replay
g.reset()
same_noise = g.generate(1024)

# New seed
g.reseed(999)

AWGN

Create an AWGN generator. Allocates state, seeds the xoshiro256++ RNG via SplitMix64, and sets up both the scalar and the AVX2 parallel streams. The initial seed is stored so awgn_reset() can reproduce the exact same stream.

Parameters:

Name Type Description Default
seed int

64-bit RNG seed. Two generators with different seeds produce statistically independent noise streams.

0
amplitude float

Per-component (Re, Im) standard deviation. Must be ≥ 0; total complex power = 2 × amplitude².

1.0

Examples:

>>> from doppler.source import AWGN
>>> gen = AWGN(seed=0, amplitude=1.0)
>>> gen.amplitude
1.0

amplitude property writable

amplitude: float

Return the current amplitude (per-component std dev).

reset

reset() -> None

Reset RNG to the seed supplied at create time. Re-runs the SplitMix64 seeding procedure with the original seed so the next awgn_generate() call produces exactly the same samples as the first call after awgn_create(). amplitude is not changed.

Examples:

>>> import numpy as np
>>> from doppler.source import AWGN
>>> gen = AWGN(seed=0, amplitude=1.0)
>>> first = gen.generate(4)
>>> gen.reset()
>>> second = gen.generate(4)
>>> bool(np.all(first == second))
True

generate

generate(
    count: int = 1, out: NDArray[complex64] | None = None
) -> NDArray[np.complex64]

Generate n complex CF32 AWGN samples. Uses Box-Muller with xoshiro256++ to fill out with independent complex Gaussians: Re and Im each have zero mean and standard deviation amplitude. Total complex power = 2 × amplitude². The AVX2 path processes 8 samples in parallel when available.

Returns:

Type Description
NDArray[complex64]

min(n, max_out) samples.

Examples:

>>> import numpy as np
>>> from doppler.source import AWGN
>>> gen = AWGN(seed=0, amplitude=1.0)
>>> out = gen.generate(1024)
>>> out.dtype
dtype('complex64')
>>> out.shape
(1024,)
>>> round(float(np.var(out.real)), 1)
1.0
>>> round(float(np.var(out.imag)), 1)
1.0

generate_max_out

generate_max_out() -> int

Conservative upper bound on generate() output size.

Returns 65536. The Python extension uses this for the initial

buffer allocation; the buffer grows on demand if n > 65536.

Returns:

Type Description
int

Output.

reseed

reseed(seed: int) -> None

Reseed the RNG and reset all xoshiro256++ state. Equivalent to calling awgn_destroy() and awgn_create(seed, amplitude) but reuses the existing allocation. amplitude is unchanged.

Parameters:

Name Type Description Default
seed int

New 64-bit RNG seed.

required

Examples:

>>> import numpy as np
>>> from doppler.source import AWGN
>>> gen = AWGN(seed=0, amplitude=1.0)
>>> gen.reseed(42)
>>> out1 = gen.generate(4)
>>> gen2 = AWGN(seed=42, amplitude=1.0)
>>> out2 = gen2.generate(4)
>>> bool(np.all(out1 == out2))
True

state_bytes

state_bytes() -> int

Size in bytes of this object's serialized state.

The exact length get_state returns and set_state requires. It depends on how the object was constructed (state arrays are sized at construction), so read it from the instance rather than assuming a constant.

Raises RuntimeError if the AWGN has already been destroyed.

Returns:

Type Description
int

Byte length of one serialized state blob.

get_state

get_state() -> bytes

Serialize this object's mutable state to bytes.

Captures exactly the state that evolves as the object runs, so a blob taken now and restored later resumes from this point. Construction parameters are not included: restore into an object built the same way.

The blob is opaque and always state_bytes() long. Its layout is an implementation detail of the C core and is not a stable format across builds.

Raises RuntimeError if the AWGN has already been destroyed.

Returns:

Type Description
bytes

Opaque snapshot, state_bytes() bytes long.

set_state

set_state(blob: bytes) -> None

Restore mutable state from a get_state() blob.

Overwrites the live state in place; the object keeps the parameters it was constructed with. Length is validated against state_bytes() before the blob is handed to the C core, and the core may reject it as well.

Raises TypeError if blob is not bytes, ValueError if its length differs from state_bytes() or the core rejects it, and RuntimeError if the AWGN has already been destroyed.

Parameters:

Name Type Description Default
blob bytes

A get_state() blob from this type, exactly state_bytes() long.

required

destroy

destroy() -> None

Release the underlying C resources immediately.

Ordinarily unnecessary: the resources are freed when the object is garbage-collected. Call this to release them at a definite point instead, or use the object as a context manager, which calls it on exit.

Idempotent: calling it again on an already-released object does nothing. Every other method raises RuntimeError once it has run.

__enter__

__enter__() -> AWGN

Enter a context manager, returning this object.

Lets a AWGN be used in a with statement so its C resources are released deterministically on exit rather than at collection time.

Returns:

Type Description
AWGN

This same object, not a copy.

__exit__

__exit__(
    exc_type: object | None = ...,
    exc: object | None = ...,
    tb: object | None = ...,
) -> None

Exit a context manager, releasing the AWGN.

Equivalent to calling destroy(). Returns None, so an exception raised inside the with body propagates normally; this never suppresses one.

Parameters:

Name Type Description Default
exc_type object | None

Exception class, or None. Ignored.

...
exc object | None

Exception instance, or None. Ignored.

...
tb object | None

Traceback object, or None. Ignored.

...

GalleryAWGN Generator, Measuring an Error Rate, Defensibly, Carrier Loop Stress, Gallery GuidesWaveform Generator — wfmgen, Recipes, Scenes — multi-segment specs DesignDesign — pure-functional acquisition kernel (elastic fleet), API taxonomy: the DSP building-block hierarchy and its naming axis, Spectral & Measurement API Map, State Serialization — the standard bytes interface ContributingDoc examples — every snippet is tested