Example: Noise Generation¶
The light_curves() strategy wraps system
generation, light-curve computation, and noise injection into a single
step. This page walks through the noise factories and shows how each
noise type affects a transit light curve.
Setup¶
All examples on this page use the same deterministic system and time grid:
import numpy as np
from hypothesis import strategies as st
from lightcurve_strategies import (
bodies,
centrals,
light_curves,
surfaces,
surface_systems,
white_noise,
red_noise,
combined_noise,
sq_exp_kernel,
matern32_kernel,
)
time = np.linspace(-0.2, 0.2, 500)
system_strategy = surface_systems(
central=centrals(mass=st.just(1.0), radius=st.just(1.0)),
central_surface=surfaces(u=st.just((0.1, 0.3))),
body=st.tuples(
bodies(
period=st.just(3.0),
radius=st.just(0.1),
time_transit=st.just(0.0),
impact_param=st.just(0.3),
),
surfaces(),
),
min_bodies=1,
max_bodies=1,
)
White noise¶
white_noise() adds independent Gaussian
noise to each flux measurement. The scale parameter controls the
standard deviation:
data = light_curves(
time=time,
system=system_strategy,
noise=st.just(white_noise(scale=5e-4)),
).example()
# data.flux — clean transit
# data.flux_with_noise — transit + noise
# data.noise — the noise realisation alone
import numpy as np
import matplotlib.pyplot as plt
from hypothesis import strategies as st
from lightcurve_strategies import (
bodies, centrals, surfaces, surface_systems,
light_curves, white_noise,
)
time = np.linspace(-0.2, 0.2, 500)
system_strategy = surface_systems(
central=centrals(mass=st.just(1.0), radius=st.just(1.0)),
central_surface=surfaces(u=st.just((0.1, 0.3))),
body=st.tuples(
bodies(period=st.just(3.0), radius=st.just(0.1),
time_transit=st.just(0.0), impact_param=st.just(0.3)),
surfaces(),
),
min_bodies=1, max_bodies=1,
)
data = light_curves(
time=time,
system=system_strategy,
noise=st.just(white_noise(scale=5e-4)),
seed=st.just(42),
).example()
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 5), sharex=True,
gridspec_kw={"height_ratios": [3, 1]})
ax1.plot(time, data.flux, label="Clean", linewidth=1.5)
ax1.scatter(time, data.flux_with_noise, s=1, alpha=0.6, label="With noise")
ax1.set_ylabel("Relative flux")
ax1.set_title("White noise (scale = 5e-4)")
ax1.legend()
ax2.plot(time, data.noise, color="tab:red", linewidth=0.5)
ax2.set_xlabel("Time (days)")
ax2.set_ylabel("Noise")
ax2.axhline(0, color="gray", linewidth=0.5, linestyle=":")
fig.tight_layout()
Combined noise¶
combined_noise() sums multiple noise
sources. This is useful for modelling, e.g., white photon noise on
top of correlated stellar variability:
noise_fn = combined_noise(
white_noise(scale=3e-4),
red_noise(kernel=sq_exp_kernel(amplitude=3e-4, length_scale=0.05),
jitter=1e-10),
)
data = light_curves(
time=time,
system=system_strategy,
noise=st.just(noise_fn),
).example()
import numpy as np
import matplotlib.pyplot as plt
from hypothesis import strategies as st
from lightcurve_strategies import (
bodies, centrals, surfaces, surface_systems,
light_curves, white_noise, red_noise, combined_noise, sq_exp_kernel,
)
time = np.linspace(-0.2, 0.2, 500)
system_strategy = surface_systems(
central=centrals(mass=st.just(1.0), radius=st.just(1.0)),
central_surface=surfaces(u=st.just((0.1, 0.3))),
body=st.tuples(
bodies(period=st.just(3.0), radius=st.just(0.1),
time_transit=st.just(0.0), impact_param=st.just(0.3)),
surfaces(),
),
min_bodies=1, max_bodies=1,
)
fig, axes = plt.subplots(1, 3, figsize=(12, 3.5), sharey=True)
configs = [
("White only", white_noise(scale=3e-4)),
("Red only", red_noise(kernel=sq_exp_kernel(3e-4, 0.05), jitter=1e-10)),
("Combined", combined_noise(
white_noise(scale=3e-4),
red_noise(kernel=sq_exp_kernel(3e-4, 0.05), jitter=1e-10),
)),
]
for ax, (title, noise_fn) in zip(axes, configs):
data = light_curves(
time=time,
system=system_strategy,
noise=st.just(noise_fn),
seed=st.just(42),
).example()
ax.plot(time, data.flux, linewidth=1.5, label="Clean")
ax.scatter(time, data.flux_with_noise, s=1, alpha=0.6, label="Noisy")
ax.set_title(title)
ax.set_xlabel("Time (days)")
ax.legend(fontsize=8)
axes[0].set_ylabel("Relative flux")
fig.tight_layout()
Deterministic seeds¶
Noise is fully reproducible when you fix the seed parameter.
This is useful for regression tests — the same seed always produces
the same noise realisation:
strategy = light_curves(
time=time,
system=system_strategy,
noise=st.just(white_noise(scale=1e-3)),
seed=st.just(12345),
)
data1 = strategy.example()
data2 = strategy.example()
assert np.array_equal(data1.noise, data2.noise) # always True
When seed is left as the default (st.integers(0, 2**32 - 1)),
Hypothesis controls the seed, providing determinism within each test
run and shrinkability on failure.
Property-based test with noise¶
Here is a property-based test that asserts a physical invariant: even with noise added, the clean flux should never exceed the out-of-transit baseline. The noise realisation is checked separately:
from hypothesis import given, settings
@given(
data=light_curves(
time=time,
system=system_strategy,
noise=st.just(white_noise(scale=1e-3)),
)
)
@settings(max_examples=10)
def test_clean_flux_bounded(data):
baseline = data.flux[0]
assert np.all(data.flux <= baseline + 1e-6)
# Verify noise was applied correctly
np.testing.assert_allclose(
data.flux_with_noise, data.flux + data.noise
)