Source code for chaotic_pfc.dynamics.signals

"""
signals.py
==========
Generators for the information-bearing messages used throughout the
chaotic communication pipeline.

Two waveforms are provided:

* :func:`binary_message` — a square-wave BPSK-style message taking values
  in ``{-1, +1}`` with a fixed bit period.
* :func:`sinusoidal_message` — a pure cosine/sine probe useful for
  spectral-response measurements.

Both functions return NumPy arrays of length ``N`` so they can be fed
directly to :func:`chaotic_pfc.comms.transmitter.transmit`.
"""

from __future__ import annotations

import numpy as np
from numpy.typing import NDArray


[docs] def binary_message(N: int, period: int = 20) -> NDArray: """Generate a periodic square-wave binary message. The output takes values in ``{+1, -1}``, with the first half of each period at ``+1`` and the second half at ``-1``. This is the standard BPSK-style message used by :func:`chaotic_pfc.comms.transmitter.transmit`. Parameters ---------- N Total number of samples to produce. period Length of one full ``+1`` / ``-1`` cycle. Must be a positive even integer; each half-cycle holds ``period // 2`` samples. Returns ------- ndarray, shape (N,) The message samples, each ``+1.0`` or ``-1.0``. Raises ------ ValueError If ``period`` is not a positive even integer. Examples -------- >>> binary_message(8, period=4) array([ 1., 1., -1., -1., 1., 1., -1., -1.]) """ if period <= 0 or period % 2 != 0: raise ValueError(f"period must be a positive even integer, got {period}") half = period // 2 block = np.concatenate([np.ones(half), -np.ones(half)]) num_blocks = int(np.ceil(N / period)) return np.tile(block, num_blocks)[:N]
[docs] def sinusoidal_message(N: int, normalised_freq: float = 0.1) -> NDArray: """Generate a single-tone sinusoidal probe signal. Useful for frequency-response characterisation: feeding the output of this function through the transmitter/channel/receiver chain shows the system's gain and phase at one specific frequency. Parameters ---------- N Number of samples to produce. normalised_freq Frequency in cycles per sample. Must satisfy ``0 < f < 0.5`` to avoid aliasing (Nyquist at ``f = 0.5``). The default of ``0.1`` gives ten samples per cycle. Returns ------- ndarray, shape (N,) The samples ``sin(2π · normalised_freq · n)`` for ``n = 0, 1, …, N − 1``. Raises ------ ValueError If *normalised_freq* is not strictly between 0 and 0.5. """ if not (0 < normalised_freq < 0.5): raise ValueError(f"normalised_freq must be in (0, 0.5), got {normalised_freq}") n = np.arange(N) return np.sin(2.0 * np.pi * normalised_freq * n)