Atmosphere¶
Overview¶
The Atmosphere object simulates one or more turbulent layers following Von Kármán statistics. Each layer is independent and has its own altitude, wind speed, wind direction, and fractional Cn² contribution. The total turbulence strength is parameterised by the Fried parameter r0 and the outer scale L0, both expressed at 500 nm.
The correct way to include the atmosphere in a simulation is to place it directly in the propagation chain, before the Telescope:
ngs ** atm * tel * wfs
When atm.relay(ngs) is called, it updates the source’s OPD and EM field with the atmospheric phase contribution. The pupil is only applied once the source passes through tel — propagating through atm alone (without tel) gives an unpupilled wavefront.
Note
The atmosphere does not advance its phase screens automatically during propagation. update() must be called explicitly at the start of each loop iteration before propagating.
The legacy tel + atm coupling (which updated tel.OPD directly) is retained for backward compatibility only. New code should always use the chain syntax above.
Three propagation modes are available:
Geometric only (default) — phase-only, no scintillation.
Diffractive — angular-spectrum propagation through each layer; produces amplitude fluctuations (scintillation).
Diffractive + geometric backup — same as mode 2 but also stores the purely geometric phase for comparison.
Key concepts¶
Phase screens — each layer stores an infinitely-scrolling phase screen computed from a power-law spectral model. The screen is larger than the telescope aperture to allow continuous scrolling without repetition.
relay() — updates the source OPD and EM field with the atmospheric phase. Called automatically by
ngs * atm. Does not advance the phase screens.update() — advances all phase screens by one AO time step. Must be called explicitly each loop iteration. Also accepts a user-supplied OPD to inject an arbitrary wavefront directly (bypassing the turbulence screens).
Cone effect — when a
Sourceis an LGS (finite altitude), the footprint on each layer is scaled accordingly via bilinear interpolation.Multi-source / asterism — when an
Asterismis propagated, each source receives an independent OPD slice computed from the atmosphere layers according to its sky coordinates and altitude.Elevation — the effective
r0and layer altitudes are scaled bysin(elevation); default is 90° (zenith).On-the-fly updates — wind speed, direction, r0, and elevation can be changed at runtime.
Quick start¶
from OOPAO.Atmosphere import Atmosphere
atm = Atmosphere(
telescope = tel,
r0 = 0.15, # Fried parameter [m] at 500 nm
L0 = 25.0, # Outer scale [m]
windSpeed = [10.0, 5.0], # [m/s] per layer
fractionalR0 = [0.6, 0.4], # Cn2 weights (must sum to 1)
windDirection = [0.0, 45.0], # [deg] per layer
altitude = [0.0, 5000.0], # [m] per layer
)
# Closed-loop example
for i in range(nLoop):
atm.update() # advance phase screens
ngs ** atm * tel * wfs # reset, then propagate
dm.coefs -= gain * M2C @ wfs.signal # apply correction
# Inject a user-defined OPD instead of turbulence
atm.update(my_opd_map) # positional
atm.update(OPD=my_opd_map) # keyword — both are equivalent
ngs ** atm * tel * wfs
# Change wind speed on the fly
atm.windSpeed = [12.0, 7.0]
# Legacy coupling (backward compatibility only)
tel + atm # tel.OPD mirrors atm.OPD — avoid in new code
tel - atm # decouple
API reference¶
- class OOPAO.Atmosphere.Atmosphere(telescope, r0, L0, windSpeed, fractionalR0, windDirection, altitude, src=None, param=None, elevation=90.0, mode=2, angular_spectrum_propagation=False, geometric_phase_backup=False, unwrap_diffractive_phase=False, rytov_var=None, rytov_wvl=500e-9)[source]¶
Multi-layer turbulent atmosphere with Von Kármán statistics.
- Parameters:
telescope (Telescope) – Telescope object. Carries pupil definition, pixel size, and sampling time. Required at construction even when using the chain syntax.
r0 (float) – Fried parameter in metres at 500 nm at the specified elevation.
L0 (float) – Outer scale in metres.
fractionalR0 (list[float]) – Cn² profile weights for each layer. Values must sum to 1.
windDirection (list[float]) – Wind direction for each layer in degrees.
src (Source or None) – Source object. If
None, usestelescope.src. DefaultNone.param (object or None) – Parameter file object. When provided, covariance matrices are saved/loaded from disk to avoid recomputation. Default
None.elevation (float) – Telescope elevation in degrees. Scales effective r0 and layer altitudes. Default
90.0(zenith).mode (int) – Spectral model for phase screen generation.
1uses aotools;2uses the OOPAO internal model (default).angular_spectrum_propagation (bool) – If
True, enables diffractive propagation and scintillation computation. DefaultFalse.geometric_phase_backup (bool) – If
True, stores the geometric phase alongside the diffractive phase. Requiresangular_spectrum_propagation=True. DefaultFalse.unwrap_diffractive_phase (bool) – If
True, unwraps the diffractive phase after propagation. DefaultFalse.rytov_var (float or None) – Override for the Rytov scintillation variance. If
None, computed from Cn² and wavelength. DefaultNone.rytov_wvl (float) – Wavelength at which the Rytov variance is computed, in metres. Default
500e-9.
Key properties
- OPD: numpy.ndarray¶
Current 2-D OPD map in metres (sum of all layers projected onto the telescope pupil). Used by the legacy
tel + atmcoupling. In the chain syntax, the source OPD is updated directly byrelay().
- elevation: float¶
Telescope elevation in degrees. Changing this rescales the effective r0 and layer altitudes.
Methods
- relay(src)[source]¶
Core propagation method. Called automatically by
src * atm. Updates the source OPD and EM field with the atmospheric phase contribution. Does not advance the phase screens — callupdate()first.
- update(OPD=None)[source]¶
Advance all phase screens by one AO time step (
tel.samplingTime).If
OPDis supplied, it is used directly to set the source OPD and phase, bypassing the turbulence screens entirely. This allows injection of arbitrary wavefronts into the propagation chain.- Parameters:
OPD (numpy.ndarray or None) – User-supplied 2-D OPD map in metres. If
None(default), the phase screens are advanced normally.
Both calling styles are equivalent:
atm.update(my_opd_map) # positional atm.update(OPD=my_opd_map) # keyword
- generateNewPhaseScreen(seed)[source]¶
Regenerate all phase screens from a new random seed.
- Parameters:
seed (int) – Random seed.
- display()¶
Display the current phase screen for each layer as a matplotlib figure.
Operator summary
Note
All wavelength-dependent atmosphere parameters (r0, seeing) are expressed at 500 nm by convention, regardless of the source wavelength. The
Sourcewavelength is used only when computing the phase from the OPD (phase = OPD * 2π / wavelength).