Architecture & concepts
The pipeline
A TelescopeSim is a linear chain of pupil-plane stages followed by a
controlled fan-out at the pupil → focal boundary:
[aperture (+ spider)]
|
v
[correctors: c1 → c2 → ... → cN]
|
v
[coronagraph (optional)]
|
v
pupil_post_coro
|
+----+----+ ... +----+
v v v
fp_1 fp_2 ... fp_K ← named focal planes
| | |
v v v
taps taps ... taps ← OutputTap(s)
| | |
v v v
post post ... post ← ordered PostProcessor list per output
The chain is strictly linear through the pupil-plane stages. The only fan-out is at the pupil → focal boundary, where multiple named focal planes can consume the same pupil-plane wavefront. This subsumes the per-filter loop, mixed angular/physical detector layouts (e.g. a science detector + a fiber-coupling plane), and pre-coro / post-coro diagnostics.
Stages and the registry
Each pipeline stage type has an abstract base class
(telescope_sim.abc) and a small registry of concrete
implementations:
Kind |
ABC |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
To add a new implementation:
from telescope_sim.abc import Aperture
from telescope_sim.registry import register
@register("aperture", "my_custom")
class MyCustomAperture(Aperture):
def build(self, pupil_grid):
...
Users can register their own implementations from their own packages
without modifying telescope-sim.
The corrector role system
Each corrector has two orthogonal “role” attributes that control how it participates in a sample:
wavefront_roleControls how actuator state gets set when
sample()runs."actuate"— set by the ML model (caller’sactuationsdict)."impose"— set by the caller via a separate channel (e.g. an environment-supplied aberration)."fit"— lstsq-fit to another corrector’s surface or to a named wavefront’s phase. Requiresfit_source.
target_strategyControls what this corrector contributes to the Y output dict returned by
sample()."none"— not in Y."actuators"— echo back current actuator state."actuators_plus_residual_fit"—actuators + fit_surface(cumulative_phase_pre_self). Useful for ML training where the “ideal” actuation is the model’s command plus the residual disturbance."residual_fit_only"— just the residual fit.
This two-axis design covers the patterns observed across the legacy variants (PTT-imposed-with-DM-approximation, cumulative residual fits that include external atmosphere, stacked imposers with a single fitting DM, …) without bespoke logic per case.
Reference PSF
Each focal plane precomputes its at-rest “reference PSF” once at
construction time: every corrector flattened, the coronagraph (if any)
bypassed, and the resulting summed intensity used for Strehl
normalization. Post-processors that depend on the reference (e.g.
max_intensity_norm) read it from the
PipelineContext passed into them.
Atmosphere
Atmospheres are not correctors. They enter the simulation as a
per-sample input on sample():
out = sim.sample(atmos=my_atmosphere, ...)
The contract for atmos is loose by design: any wf→wf callable
(typically a hcipy.InfiniteAtmosphericLayer or
hcipy.MultiLayerAtmosphere, but anything that maps a
hcipy.Wavefront to a modified one works) is applied at the front of
the chain, before any corrector. The caller owns time evolution — v2
holds no atmosphere state.
If the atmosphere also exposes phase_for(lam) returning
HCIPy-convention phase (2π·OPD/lam), its OPD is seeded into the
per-corrector cumulative-OPD stream. A downstream fit-role corrector
with fit_source="cumulative_phase_pre_self" will then naturally fit
to (and, applied with the role’s automatic sign-flip, cancel) the
atmosphere — no bespoke “atmosphere corrector” needed. Atmospheres
without phase_for still modify the wavefront, but only the wavefront
sees them; fit-role correctors see only the corrector chain.
The reference PSF used for Strehl normalization is never atmospheric:
it is cached once at sim-build with atmos=None implicit.