Source code for neuralfieldmanifold.simulation.mixing
"""Oscillatory AR sources and distance-weighted linear sensor mixtures."""
import numpy as np
from .._validation import positive_integer, sampling_rate
[docs]
def oscillatory_ar(
frequencies_hz,
pole_radii,
*,
sampling_rate_hz,
n_samples,
noise_std=0.05,
seed=0,
random_initialization=False,
amplitude=1.0,
):
"""Generate an oscillatory AR source scaled to the requested amplitude.
The AR characteristic polynomial combines conjugate pole pairs.
The output is divided by its standard deviation and multiplied
by amplitude.
``random_initialization=True`` initializes the lag history with Gaussian
draws. Otherwise the initial history is zero.
"""
fs = sampling_rate(sampling_rate_hz)
n_samples = positive_integer(n_samples, "n_samples")
frequencies, radii = np.asarray(frequencies_hz), np.asarray(pole_radii)
if (
frequencies.ndim != 1
or frequencies.shape != radii.shape
or not frequencies.size
):
raise ValueError("Expected matching nonempty mode vectors")
if (
not np.isfinite(frequencies).all()
or not np.isfinite(radii).all()
or np.any((frequencies <= 0) | (frequencies >= fs / 2))
or np.any((radii <= 0) | (radii > 1))
or noise_std < 0
):
raise ValueError("Invalid mode parameters")
rng = np.random.default_rng(seed)
poles = [
root
for f, r in zip(frequencies, radii)
for root in (
r * np.exp(1j * 2 * np.pi * f / fs),
r * np.exp(-1j * 2 * np.pi * f / fs),
)
]
coefficients = np.real(-np.poly(poles)[1:])
order = len(coefficients)
if n_samples <= order:
raise ValueError("n_samples must exceed twice the mode count")
x = np.zeros(n_samples)
noise = rng.normal(0, noise_std, n_samples)
if random_initialization:
x[:order] = rng.normal(0, noise_std, order)
if len(frequencies) == 1:
angle = 2 * np.pi * frequencies[0] / fs
first, second = 2 * radii[0] * np.cos(angle), -(radii[0] ** 2)
for t in range(2, n_samples):
x[t] = first * x[t - 1] + second * x[t - 2] + noise[t]
else:
for t in range(order, n_samples):
x[t] = sum(coefficients[i] * x[t - i - 1] for i in range(order)) + noise[t]
return amplitude * x / (np.std(x) + 1e-12)
[docs]
def mixing_matrix(*, n_sensors, n_sources, spatial_spread):
"""Row-normalized Gaussian distance weights on uniformly spaced locations."""
n_sensors = positive_integer(n_sensors, "n_sensors")
n_sources = positive_integer(n_sources, "n_sources")
if not np.isfinite(spatial_spread) or spatial_spread <= 0:
raise ValueError("spatial_spread must be positive")
sources, sensors = np.linspace(0, 1, n_sources), np.linspace(0, 1, n_sensors)
distances = np.abs(sensors[:, None] - sources[None, :])
weights = np.exp(-(distances**2) / (2 * spatial_spread**2))
return weights / weights.sum(axis=1, keepdims=True)