Concepts and architecture
The four building blocks
Object |
Role |
|---|---|
|
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 |
|
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 |
|
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 |
|
The coronagraph: raw dark-hole 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 |
|---|---|---|---|
|
A disc of diameter \(\lambda/D\), the default |
19.6 |
0.456 |
|
A disc of radius 0.7 \(\lambda/D\) |
38.4 |
0.678 |
|
A square of side 2.44 \(\lambda/D\) (13 x 13 pixels), the first Airy dark ring |
169 |
0.850 |
|
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