Concepts and architecture

The four building blocks

Object

Role

Telescope

The optics: primary diameter, collecting area (already reduced by the Lyot stop), focal ratio and length, the filter’s central wavelength, and the resolution element resel, the area of one PSF core, \(\pi(\lambda/2D)^2\).

Sensor

The detector and the optical path to it: read noise, dark current, gain, well depth, the optical bandpass (mirror coatings, filter and detector quantum efficiency, or one of the measured ESC filter curves) and the photometric aperture every noise term uses, with the encircled energy ee_fraction it credits to a point source.

Scene

What is on the sky: the host star, the planet (a magnitude difference relative to the star) and the zodiacal and exozodiacal backgrounds. Each is a SceneElement wrapping a synphot spectrum and a magnitude.

Coronagraph

The coronagraph: raw dark-hole contrast raw_dh_contrast, post-processing gain ppgain, planet separation and the focal-plane mask throughput curve. See Coronagraph and contrast.

A Simulation owns one of each, plus a noise model, and turns them into count rates, signal-to-noise and integration times. The components are reachable as sim.telescope, sim.sensor, sim.scene, sim.coronagraph and sim.noise_model.

The photometric aperture

Every noise term is evaluated over the same aperture, chosen with sensor.aperture. The aperture sets how many detector pixels contribute dark current and read noise, and the encircled energy sets what fraction of a point source’s light is credited to it. At the default configuration (5.0 pixels per \(\lambda/D\)):

Name

Geometry

Pixels

Encircled energy

resel

A disc of diameter \(\lambda/D\), the default

19.6

0.456

optimal

A disc of radius 0.7 \(\lambda/D\)

38.4

0.678

box

A square of side 2.44 \(\lambda/D\) (13 x 13 pixels), the first Airy dark ring

169

0.850

matched

A matched filter: the effective pixel count of a PSF-weighted sum, credited with the full flux

70.5

1.0

encircled_energy overrides the tabulated value (it must lie in (0, 1]), for instance when a measured PSF gives a different number.

Constructing a simulation

There are two convenience constructors:

from esc_etc import Simulation

# All four components from the bundled Lazuli config (3 m telescope + ESC):
sim = Simulation.from_default(hoststarmag=-0.353, planetdeltamag=20.0,
                              contrast=1e-8, ppgain=10, separation=0.4,
                              arm="arm_a")

# From components you build yourself:
sim = Simulation.from_components(telescope, sensor, scene, coronagraph)

from_components is the way in for a custom instrument: build each piece with its own from_default or from_config (see Configuration and filters), or construct it directly, then assemble.

The noise model

Simulation takes a noise_model argument, by default AnalyticCoronagraphNoiseModel. Any object with the same snr and int_time_for_snr methods can be passed instead, for example an image-based model, without changing the simulation class. See Signal-to-noise and integration time.

Inspecting and updating parameters

Each component tracks its tunable parameters, and update() changes them with child__key double-underscore syntax, where the prefix names the component the key belongs to:

sim.update(coronagraph__separation=0.6)   # coronagraph knob
sim.update(sensor__read_noise=5)          # detector knob, in the sensor's own units
sim.update(scene__star__mag=-0.2)         # scene element (routed via Scene)

sim.sensor.describe()                     # print a component's tunable params
sim.reset()                               # restore every component

Changing raw_dh_contrast rescales the speckle rate, changing ppgain moves the noise floor, and changing separation picks a new off-axis throughput, so the next SNR call reflects the change. A value the component refuses (encircled_energy outside (0, 1], a non-positive ppgain) raises ValueError and leaves the object unchanged.

Times

total_time and frame_time may be astropy quantities in any time unit or plain numbers, which are read as seconds: sim.get_snr(3600, 100) and sim.get_snr(1 * u.h, 100 * u.s) are the same call.

Count rates and the electron budget

Beneath the SNR helpers, count_rate_components() returns a CountRates dataclass with the per-second electron rates for the planet source, the zodi and exozodi backgrounds, the residual speckle light, and the host. The host rate is what the unocculted star would deliver through the same aperture; it is only used to scale the speckles, which are raw_dh_contrast times the host. Every rate has already been multiplied by the focal-plane mask throughput \(k\). These feed the noise model, and are the first thing to look at when an SNR surprises you:

rates = sim.count_rate_components()
rates.source, rates.zodi, rates.exozodi, rates.speckle, rates.host