Instrumental noise#

The ability to add noise to your detector timestreams is supported through the function add_noise_to_observations() and the low-level versions add_noise(), add_white_noise(), and add_one_over_f_noise().

Here is a short example that shows how to add noise:

import litebird_sim as lbs
import numpy as np

# Create a simulation lasting 100 seconds
sim = lbs.Simulation(
    base_path='./output',
    start_time=0,
    duration_s=100,
    random_seed=12345,
)

# Create a detector object
det = lbs.DetectorInfo(
  net_ukrts=100,
  sampling_rate_hz=10
)

obs = sim.create_observations(detectors=[det])

# Here we add white noise using the detector
# noise parameters from the `det` object.
# We use the random number generator provided
# by `sim`, which is initialized with the
# seed we passed to the Simulation constructor
# to ensure repeatability.
lbs.noise.add_noise_to_observations(obs, 'white', random=sim.random)

for i in range(10):
    print(f"{obs[0].tod[0][i]:.5e}")
5.65263e-04
-1.54522e-04
3.42276e-04
-1.42274e-04
-1.71110e-04
8.72188e-05
-1.23400e-04
-6.99311e-05
6.58389e-05
5.51306e-04

Note that we pass sim.random as the number generator to use. This is a member variable that is initialized by the constructor of the class Simulation, and it is safe to be used with multiple MPI processes as it ensures that each process has its own random number generator with a different seed. You can also pass another random number generator, as long as it has the normal method. More information on the generation of random numbers can be found in Random numbers.

To add white noise using a custom white noise sigma, in µK, we can call the low level function directly:

import litebird_sim as lbs

sim = lbs.Simulation(
    base_path='./output',
    start_time=0,
    duration_s=100,
    random_seed=12345,
)

det = lbs.DetectorInfo(
  net_ukrts=100,
  sampling_rate_hz=10,
)

obs = sim.create_observations(detectors=[det])

custom_sigma_uk = 1234
lbs.noise.add_white_noise(obs[0].tod[0], custom_sigma_uk, random=sim.random)

We can also add 1/f noise using a very similar call to the above:

import litebird_sim as lbs

sim = lbs.Simulation(
    base_path='./output',
    start_time=0,
    duration_s=100,
    random_seed=12345,
)

det = lbs.DetectorInfo(
  net_ukrts=100,
  sampling_rate_hz=10,
  alpha=1,
  fknee_mhz=10
)

obs = sim.create_observations(detectors=[det])

# Here we add 1/f noise using the detector noise
# parameters from the detector object
lbs.noise.add_noise_to_observations(obs, 'one_over_f', random=sim.random)

Again, to generate noise with custom parameters, we can either use the low-level function or edit the Observation object to contain the desired noise parameters.

import litebird_sim as lbs
import numpy as np

sim = lbs.Simulation(
    base_path='./output',
    start_time=0,
    duration_s=100,
    random_seed=12345,
)

det = lbs.DetectorInfo(
  net_ukrts=100,
  sampling_rate_hz=10,
  alpha=1,
  fknee_mhz=10,
  fmin_hz=0.001,
)

obs = sim.create_observations(detectors=[det])

custom_sigma_uk = 1234
custom_fknee_mhz = 12.34
custom_alpha = 1.234
custom_fmin_hz = 0.0123

# Option 1: we call the low-level function directly
lbs.noise.add_one_over_f_noise(
    obs[0].tod[0],
    custom_fknee_mhz,
    custom_fmin_hz,
    custom_alpha,
    custom_sigma_uk,
    obs[0].sampling_rate_hz,
    sim.random,
)

# Option 2: we change the values in `obs`
obs[0].fknee_mhz[0] = custom_fknee_mhz
obs[0].fmin_hz[0] = custom_fmin_hz
obs[0].alpha[0] = custom_alpha
obs[0].net_ukrts[0] = (
    custom_sigma_uk / np.sqrt(obs[0].sampling_rate_hz)
)

lbs.noise.add_noise_to_observations(obs, 'one_over_f', random=sim.random)

Warning

It’s crucial to grasp the distinction between the noise level in a timestream and the noise level in a map. While the latter is dependent on the former, the conversion is influenced by several factors. This understanding will empower you in your data analysis tasks.

A common mistake is to use the mission time divided by the number of pixels in the map in a call to func:.add_white_noise. This is wrong, as the noise level per pixel depends on the overall integration time, which is always less than the mission time because of cosmic ray loss, repointing maneuvers, etc. These effects reduce the number of samples in the timeline that can be used to estimate the map, but they do not affect the noise of the timeline.

Methods of the Simulation class#

The class Simulation provides the function Simulation.add_noise() which adds noise to the timelines. All the details of the noise are provided in the class observation and the interface is simplified.

import litebird_sim as lbs
from astropy.time import Time
import numpy as np

start_time = 0
time_span_s = 1000.0
sampling_hz = 10.0
nside = 128

sim = lbs.Simulation(
    start_time=start_time,
    duration_s=time_span_s,
    random_seed=12345,
)

# We pick a simple scanning strategy where the spin axis is aligned
# with the Sun-Earth axis, and the spacecraft spins once every minute
sim.set_scanning_strategy(
    lbs.SpinningScanningStrategy(
        spin_sun_angle_rad=np.deg2rad(0),
        precession_rate_hz=0,
        spin_rate_hz=1 / 60,
        start_time=start_time,
    ),
    delta_time_s=5.0,
 )

# We simulate an instrument whose boresight is perpendicular to
# the spin axis.
sim.set_instrument(
    lbs.InstrumentInfo(
        boresight_rotangle_rad=0.0,
        spin_boresight_angle_rad=np.deg2rad(90),
        spin_rotangle_rad=np.deg2rad(75),
    )
)

# A simple detector looking along the boresight direction
det = lbs.DetectorInfo(
    name="Boresight_detector",
    sampling_rate_hz=sampling_hz,
    bandcenter_ghz=100.0,
    net_ukrts=50.0,
)

sim.create_observations(detectors=det)

sim.add_noise(noise_type='one_over_f')

for i in range(5):
    print(f"{sim.observations[0].tod[0][i]:.5e}")
2.83998e-04
-7.58942e-05
1.72505e-04
-6.97704e-05
-8.41885e-05

API reference#

litebird_sim.noise.add_noise(tod, noise_type: str, sampling_rate_hz: float, net_ukrts, fknee_mhz, fmin_hz, alpha, random, scale=1.0)#

Add noise (white or 1/f) to a 2D array of floating-point values

This function sums an array of random number following a white noise model with an optional 1/f component to data, which is assumed to be a D×N array containing the TOD for D detectors, each containing N samples.

The parameter noisetype must either be white or one_over_f; in the latter case, the noise will contain a 1/f part and a white noise part.

Be sure not to include cosmic ray loss, repointing maneuvers, etc., in the value passed as net_ukrts, as these affect the integration time but not the white noise per sample.

The parameter scale can be used to introduce measurement unit conversions when appropriate. Default units: [K].

The parameter random must be specified and must be a random number generator that implements the normal method. You should typically use the random field of a Simulation object for this.

The parameters net_ukrts, fknee_mhz, fmin_hz, alpha, and scale can either be scalars or arrays; in the latter case, their size must be the same as tod.shape[0], which is the number of detectors in the TOD.

litebird_sim.noise.add_noise_to_observations(observations: Observation | List[Observation], noise_type: str, random: Generator, scale: float = 1.0, component: str = 'tod')#

Add noise of the defined type to the observations in observations

This class provides an interface to the low-level function add_noise(). The parameter observations can either be one Observation instance or a list of observations, which are typically taken from the field observations of a Simulation object. Unlike add_noise(), it is not needed to pass the noise parameters here, as they are taken from the characteristics of the detectors saved in observations. The parameter random must be specified and must be a random number generator that implements the normal method. You should typically use the random field of a Simulation object for this.

By default, the noise is added to Observation.tod. If you want to add it to some other field of the Observation class, use component:

for cur_obs in sim.observations:

# Allocate a new TOD for the noise alone cur_obs.noise_tod = np.zeros_like(cur_obs.tod)

# Ask add_noise_to_observations to store the noise # in observations.noise_tod add_noise_to_observations(sim.observations, …, component=”noise_tod”)

See add_noise() for more information.

litebird_sim.noise.add_one_over_f_noise(data, fknee_mhz: float, fmin_hz: float, alpha: float, sigma: float, sampling_rate_hz: float, random)#

Adds a 1/f noise timestream with the given f knee and alpha to data To be called from add_noise_to_observations

Parameters:
  • data – 1-D numpy array

  • fknee_mhz – knee frequency in mHz

  • fmin_hz – kmin frequency for high pass in Hz

  • alpha – low frequency spectral tilt

  • sigma – the white noise level per sample. Be sure not to include cosmic ray loss, repointing maneuvers, etc., as these affect the integration time but not the white noise per sample.

  • sampling_rate_hz – the sampling frequency of the data

  • random – a random number generator that implements the normal method. You should typically use the random field of a Simulation object for this. It must be specified

litebird_sim.noise.add_white_noise(data, sigma: float, random)#

Adds white noise with the given sigma to the array data.

To be called from add_noise_to_observations.

Parameters:
  • data – 1-D numpy array

  • sigma – the white noise level per sample. Be sure not to include cosmic ray loss, repointing maneuvers, etc., as these affect the integration time but not the white noise per sample.

  • random – a random number generator that implements the normal method. You should typically use the random field of a Simulation object for this. It must be specified

litebird_sim.noise.build_one_over_f_model(ft, freqs, fknee_mhz, fmin_hz, alpha, sigma)#
litebird_sim.noise.nearest_pow2(data)#

returns the next largest power of 2 that will encompass the full data set

data: 1-D numpy array

litebird_sim.noise.rescale_noise(net_ukrts: float, sampling_rate_hz: float, scale: float)#