jaxedith 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jaxedith/__init__.py +66 -0
- jaxedith/_version.py +24 -0
- jaxedith/config.py +91 -0
- jaxedith/core.py +471 -0
- jaxedith/count_rates.py +437 -0
- jaxedith/observation.py +426 -0
- jaxedith/scene.py +36 -0
- jaxedith/solver.py +273 -0
- jaxedith-0.0.2.dist-info/METADATA +62 -0
- jaxedith-0.0.2.dist-info/RECORD +12 -0
- jaxedith-0.0.2.dist-info/WHEEL +4 -0
- jaxedith-0.0.2.dist-info/licenses/LICENSE +21 -0
jaxedith/__init__.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""JAX ETC -- public API.
|
|
2
|
+
|
|
3
|
+
This module provides the main user-facing functions for the JAX exposure time
|
|
4
|
+
calculator. It bridges :mod:`optixstuff` hardware objects (stored as
|
|
5
|
+
equinox modules) and astrophysical scene parameters to the pure-JAX count-rate
|
|
6
|
+
and solver functions.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
-------
|
|
10
|
+
>>> from optixstuff import OpticalPath
|
|
11
|
+
>>> from jaxedith import calc_exptime, CONFIG
|
|
12
|
+
>>>
|
|
13
|
+
>>> # Build optical_path from optixstuff objects
|
|
14
|
+
>>> optical_path = OpticalPath(primary=..., coronagraph=..., detector=...)
|
|
15
|
+
>>> scene = ETCScene(F0=..., Fs_over_F0=..., Fp_over_Fs=..., ...)
|
|
16
|
+
>>>
|
|
17
|
+
>>> t_exp = calc_exptime(optical_path, scene, config=CONFIG)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from ._version import __version__
|
|
22
|
+
except ImportError:
|
|
23
|
+
__version__ = "unknown"
|
|
24
|
+
|
|
25
|
+
# ── Re-exports ────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
from .config import (
|
|
28
|
+
AYO_CONFIG,
|
|
29
|
+
CONFIG,
|
|
30
|
+
EXOSIMS_CHARACTERIZATION_CONFIG,
|
|
31
|
+
EXOSIMS_DETECTION_CONFIG,
|
|
32
|
+
ETCConfig,
|
|
33
|
+
)
|
|
34
|
+
from .core import (
|
|
35
|
+
calc_count_rates,
|
|
36
|
+
calc_exptime,
|
|
37
|
+
calc_exptime_spectrum,
|
|
38
|
+
calc_snr,
|
|
39
|
+
)
|
|
40
|
+
from .observation import (
|
|
41
|
+
calc_exptime_from_observation,
|
|
42
|
+
calc_exptime_from_system,
|
|
43
|
+
calc_snr_from_observation,
|
|
44
|
+
calc_snr_from_system,
|
|
45
|
+
observation_geometry,
|
|
46
|
+
)
|
|
47
|
+
from .scene import ETCScene
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"AYO_CONFIG",
|
|
51
|
+
"CONFIG",
|
|
52
|
+
"EXOSIMS_CHARACTERIZATION_CONFIG",
|
|
53
|
+
"EXOSIMS_DETECTION_CONFIG",
|
|
54
|
+
"ETCConfig",
|
|
55
|
+
"ETCScene",
|
|
56
|
+
"__version__",
|
|
57
|
+
"calc_count_rates",
|
|
58
|
+
"calc_exptime",
|
|
59
|
+
"calc_exptime_from_observation",
|
|
60
|
+
"calc_exptime_from_system",
|
|
61
|
+
"calc_exptime_spectrum",
|
|
62
|
+
"calc_snr",
|
|
63
|
+
"calc_snr_from_observation",
|
|
64
|
+
"calc_snr_from_system",
|
|
65
|
+
"observation_geometry",
|
|
66
|
+
]
|
jaxedith/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.0.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 2)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
jaxedith/config.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""ETCConfig: equinox module that selects the ETC equation variant.
|
|
2
|
+
|
|
3
|
+
The ``variant`` field is a plain Python string used for trace-time dispatch —
|
|
4
|
+
it selects which solver function to call rather than branching inside a single
|
|
5
|
+
function. This means JIT compiles only the chosen code path and ``vmap``
|
|
6
|
+
traces only one solver, avoiding dead-branch overhead.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import equinox as eqx
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ETCConfig(eqx.Module):
|
|
13
|
+
"""Configuration that determines which ETC equation variant to use.
|
|
14
|
+
|
|
15
|
+
The ``variant`` field drives solver dispatch at trace time:
|
|
16
|
+
|
|
17
|
+
- ``"ayo"`` — AYO / Stark et al. equations (ADI background doubling,
|
|
18
|
+
data-driven noise floor).
|
|
19
|
+
- ``"jaxedith"`` — Same as AYO plus exozodi noise floor extension.
|
|
20
|
+
- ``"exosims_det"`` — EXOSIMS detection (no ADI factor, speckle residual).
|
|
21
|
+
- ``"exosims_char"`` — EXOSIMS characterization (Cp added to Cb).
|
|
22
|
+
|
|
23
|
+
All other fields are static parameters passed directly to the selected
|
|
24
|
+
solver — no boolean flags or ``jnp.where`` branching.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
variant: Equation variant string (see above).
|
|
28
|
+
bg_multiplier: ADI background multiplier. AYO/jaxedith: 2.0;
|
|
29
|
+
EXOSIMS: 1.0.
|
|
30
|
+
ppfact: Post-processing factor (EXOSIMS only).
|
|
31
|
+
stability_fact: Wavefront stability factor (EXOSIMS only).
|
|
32
|
+
overhead_multi: Multiplicative overhead factor.
|
|
33
|
+
overhead_fixed_s: Fixed overhead in seconds.
|
|
34
|
+
n_rolls: Number of telescope rolls.
|
|
35
|
+
include_exozodi_noise_floor: Whether the exozodi noise floor is
|
|
36
|
+
included (jaxedith extension).
|
|
37
|
+
npix_multiplier: Pixel count correction factor for the photometric
|
|
38
|
+
aperture. ETC-specific, not a hardware property.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# --- Solver variant (trace-time dispatch) ---
|
|
42
|
+
variant: str = "jaxedith"
|
|
43
|
+
|
|
44
|
+
# --- Equation parameters ---
|
|
45
|
+
bg_multiplier: float = 2.0
|
|
46
|
+
ppfact: float = 1.0
|
|
47
|
+
stability_fact: float = 1.0
|
|
48
|
+
|
|
49
|
+
# --- Overheads ---
|
|
50
|
+
overhead_multi: float = 1.0
|
|
51
|
+
overhead_fixed_s: float = 0.0
|
|
52
|
+
n_rolls: int = 1
|
|
53
|
+
|
|
54
|
+
# --- Exozodi noise floor (jaxedith extension) ---
|
|
55
|
+
include_exozodi_noise_floor: bool = True
|
|
56
|
+
|
|
57
|
+
# --- Aperture correction ---
|
|
58
|
+
npix_multiplier: float = 1.0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Presets
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
AYO_CONFIG = ETCConfig(
|
|
66
|
+
variant="ayo",
|
|
67
|
+
bg_multiplier=2.0,
|
|
68
|
+
include_exozodi_noise_floor=False,
|
|
69
|
+
)
|
|
70
|
+
"""AYO preset — original Stark et al. equations without exozodi noise floor."""
|
|
71
|
+
|
|
72
|
+
CONFIG = ETCConfig(
|
|
73
|
+
variant="jaxedith",
|
|
74
|
+
bg_multiplier=2.0,
|
|
75
|
+
include_exozodi_noise_floor=True,
|
|
76
|
+
)
|
|
77
|
+
"""jaxedith preset — AYO equations plus exozodi noise floor."""
|
|
78
|
+
|
|
79
|
+
EXOSIMS_DETECTION_CONFIG = ETCConfig(
|
|
80
|
+
variant="exosims_det",
|
|
81
|
+
bg_multiplier=1.0,
|
|
82
|
+
include_exozodi_noise_floor=False,
|
|
83
|
+
)
|
|
84
|
+
"""EXOSIMS detection preset — no ADI factor, speckle residual instead of noise floor."""
|
|
85
|
+
|
|
86
|
+
EXOSIMS_CHARACTERIZATION_CONFIG = ETCConfig(
|
|
87
|
+
variant="exosims_char",
|
|
88
|
+
bg_multiplier=1.0,
|
|
89
|
+
include_exozodi_noise_floor=False,
|
|
90
|
+
)
|
|
91
|
+
"""EXOSIMS characterization preset — Cp added to Cb."""
|
jaxedith/core.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"""JAX ETC -- core orchestration functions.
|
|
2
|
+
|
|
3
|
+
This module contains the main user-facing functions for the JAX exposure time
|
|
4
|
+
calculator. It bridges :mod:`optixstuff` hardware objects and astrophysical
|
|
5
|
+
scene parameters to the pure-JAX count-rate and solver functions.
|
|
6
|
+
|
|
7
|
+
All public functions accept ``optixstuff.OpticalPath`` and ``ETCScene``
|
|
8
|
+
eqx.Modules directly, making the entire pipeline JIT-able end-to-end via
|
|
9
|
+
``eqx.filter_jit``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import jax
|
|
13
|
+
import jax.numpy as jnp
|
|
14
|
+
from hwoutils.constants import nm2m, rad2arcsec
|
|
15
|
+
|
|
16
|
+
from .config import CONFIG
|
|
17
|
+
from .count_rates import (
|
|
18
|
+
count_rate_binary,
|
|
19
|
+
count_rate_detector,
|
|
20
|
+
count_rate_exozodi,
|
|
21
|
+
count_rate_planet,
|
|
22
|
+
count_rate_stellar_leakage,
|
|
23
|
+
count_rate_thermal,
|
|
24
|
+
count_rate_zodi,
|
|
25
|
+
noise_floor_exozodi,
|
|
26
|
+
noise_floor_stellar,
|
|
27
|
+
noise_floor_total,
|
|
28
|
+
photon_counting_time,
|
|
29
|
+
speckle_residual,
|
|
30
|
+
)
|
|
31
|
+
from .solver import (
|
|
32
|
+
solve_exptime_ayo,
|
|
33
|
+
solve_exptime_exosims_char,
|
|
34
|
+
solve_exptime_exosims_det,
|
|
35
|
+
solve_snr_ayo,
|
|
36
|
+
solve_snr_exosims_char,
|
|
37
|
+
solve_snr_exosims_det,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# -- Trace-time dispatch ------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _dispatch_exptime(Cp, Cb, Cnf, Csp, snr, config):
|
|
44
|
+
"""Select and call the correct solve_exptime variant at trace time."""
|
|
45
|
+
kwargs = dict(
|
|
46
|
+
overhead_multi=config.overhead_multi,
|
|
47
|
+
overhead_fixed=config.overhead_fixed_s,
|
|
48
|
+
n_rolls=config.n_rolls,
|
|
49
|
+
)
|
|
50
|
+
if config.variant in ("ayo", "jaxedith"):
|
|
51
|
+
return solve_exptime_ayo(
|
|
52
|
+
Cp,
|
|
53
|
+
Cb,
|
|
54
|
+
Cnf,
|
|
55
|
+
snr,
|
|
56
|
+
bg_multiplier=config.bg_multiplier,
|
|
57
|
+
**kwargs,
|
|
58
|
+
)
|
|
59
|
+
elif config.variant == "exosims_det":
|
|
60
|
+
return solve_exptime_exosims_det(Cp, Cb, Csp, snr, **kwargs)
|
|
61
|
+
elif config.variant == "exosims_char":
|
|
62
|
+
return solve_exptime_exosims_char(Cp, Cb, Csp, snr, **kwargs)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError(f"Unknown config variant: {config.variant!r}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _dispatch_snr(Cp, Cb, Cnf_rate, Csp, t_obs, config):
|
|
68
|
+
"""Select and call the correct solve_snr variant at trace time."""
|
|
69
|
+
kwargs = dict(
|
|
70
|
+
overhead_multi=config.overhead_multi,
|
|
71
|
+
overhead_fixed=config.overhead_fixed_s,
|
|
72
|
+
n_rolls=config.n_rolls,
|
|
73
|
+
)
|
|
74
|
+
if config.variant in ("ayo", "jaxedith"):
|
|
75
|
+
return solve_snr_ayo(
|
|
76
|
+
Cp,
|
|
77
|
+
Cb,
|
|
78
|
+
Cnf_rate,
|
|
79
|
+
t_obs,
|
|
80
|
+
bg_multiplier=config.bg_multiplier,
|
|
81
|
+
**kwargs,
|
|
82
|
+
)
|
|
83
|
+
elif config.variant == "exosims_det":
|
|
84
|
+
return solve_snr_exosims_det(Cp, Cb, Csp, t_obs, **kwargs)
|
|
85
|
+
elif config.variant == "exosims_char":
|
|
86
|
+
return solve_snr_exosims_char(Cp, Cb, Csp, t_obs, **kwargs)
|
|
87
|
+
else:
|
|
88
|
+
raise ValueError(f"Unknown config variant: {config.variant!r}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# -- Count rate orchestration --------------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _compute_count_rates(
|
|
95
|
+
optical_path,
|
|
96
|
+
scene,
|
|
97
|
+
wavelength_nm,
|
|
98
|
+
separation_lod,
|
|
99
|
+
dlambda_nm,
|
|
100
|
+
snr,
|
|
101
|
+
config,
|
|
102
|
+
eps_warm_T_cold=0.0,
|
|
103
|
+
):
|
|
104
|
+
"""Compute all count rates from an OpticalPath and scene.
|
|
105
|
+
|
|
106
|
+
This function reads directly from the ``OpticalPath`` pytree
|
|
107
|
+
(primary, coronagraph, detector) so the full computation is
|
|
108
|
+
JIT-traceable.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
optical_path: ``optixstuff.OpticalPath`` eqx.Module.
|
|
112
|
+
scene: :class:`ETCScene` instance.
|
|
113
|
+
wavelength_nm: Observation wavelength [nm].
|
|
114
|
+
separation_lod: Planet separation in lam/D.
|
|
115
|
+
dlambda_nm: Bandwidth per spectral element [nm].
|
|
116
|
+
snr: Target SNR (or 1.0 for SNR-solve mode).
|
|
117
|
+
config: :class:`ETCConfig` instance.
|
|
118
|
+
eps_warm_T_cold: Warm-optics emissivity times cold transmission.
|
|
119
|
+
Defaults to 0.0 (no thermal background from optics).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Tuple of (Cp, Cb, Cnf, Csp) -- all in [e/s].
|
|
123
|
+
"""
|
|
124
|
+
coro = optical_path.coronagraph
|
|
125
|
+
primary = optical_path.primary
|
|
126
|
+
detector = optical_path.detector
|
|
127
|
+
|
|
128
|
+
# Telescope
|
|
129
|
+
area_m2 = primary.area_m2
|
|
130
|
+
throughput = optical_path.system_throughput(wavelength_nm)
|
|
131
|
+
|
|
132
|
+
# Coronagraph performance (interpax splines, fully JIT-safe)
|
|
133
|
+
core_throughput = coro.throughput(separation_lod, wavelength_nm)
|
|
134
|
+
core_area_lod2 = coro.core_area(separation_lod, wavelength_nm)
|
|
135
|
+
sky_trans = coro.occulter_transmission(separation_lod, wavelength_nm)
|
|
136
|
+
core_mean_intensity_val = coro.core_mean_intensity(separation_lod, wavelength_nm)
|
|
137
|
+
noisefloor_value = core_mean_intensity_val / config.ppfact
|
|
138
|
+
|
|
139
|
+
# Angular scales
|
|
140
|
+
lam_m = wavelength_nm * nm2m
|
|
141
|
+
lod_rad = lam_m / primary.diameter_m
|
|
142
|
+
lod_arcsec = lod_rad * rad2arcsec
|
|
143
|
+
|
|
144
|
+
# Detector-derived quantities
|
|
145
|
+
pixscale_lod = coro.pixel_scale_lod
|
|
146
|
+
n_pix = core_area_lod2 / (pixscale_lod**2) * config.npix_multiplier
|
|
147
|
+
|
|
148
|
+
# Planet signal
|
|
149
|
+
Cp = count_rate_planet(
|
|
150
|
+
scene.F0,
|
|
151
|
+
scene.Fs_over_F0,
|
|
152
|
+
scene.Fp_over_Fs,
|
|
153
|
+
area_m2,
|
|
154
|
+
throughput,
|
|
155
|
+
core_throughput,
|
|
156
|
+
dlambda_nm,
|
|
157
|
+
scene.n_channels,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Stellar leakage
|
|
161
|
+
CRbs = count_rate_stellar_leakage(
|
|
162
|
+
scene.F0,
|
|
163
|
+
scene.Fs_over_F0,
|
|
164
|
+
area_m2,
|
|
165
|
+
throughput,
|
|
166
|
+
dlambda_nm,
|
|
167
|
+
scene.n_channels,
|
|
168
|
+
core_area_lod2,
|
|
169
|
+
core_mean_intensity_val,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Zodiacal light
|
|
173
|
+
CRbz = count_rate_zodi(
|
|
174
|
+
scene.F0,
|
|
175
|
+
scene.Fzodi,
|
|
176
|
+
lod_arcsec,
|
|
177
|
+
sky_trans,
|
|
178
|
+
area_m2,
|
|
179
|
+
throughput,
|
|
180
|
+
dlambda_nm,
|
|
181
|
+
scene.n_channels,
|
|
182
|
+
core_area_lod2,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Exozodiacal light
|
|
186
|
+
CRbez = count_rate_exozodi(
|
|
187
|
+
scene.F0,
|
|
188
|
+
scene.Fexozodi,
|
|
189
|
+
lod_arcsec,
|
|
190
|
+
sky_trans,
|
|
191
|
+
area_m2,
|
|
192
|
+
throughput,
|
|
193
|
+
dlambda_nm,
|
|
194
|
+
scene.n_channels,
|
|
195
|
+
core_area_lod2,
|
|
196
|
+
scene.dist_pc,
|
|
197
|
+
scene.sep_arcsec,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Binary
|
|
201
|
+
CRbbin = count_rate_binary(
|
|
202
|
+
scene.F0,
|
|
203
|
+
scene.Fbinary,
|
|
204
|
+
sky_trans,
|
|
205
|
+
area_m2,
|
|
206
|
+
throughput,
|
|
207
|
+
dlambda_nm,
|
|
208
|
+
scene.n_channels,
|
|
209
|
+
core_area_lod2,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Thermal
|
|
213
|
+
CRbth = count_rate_thermal(
|
|
214
|
+
wavelength_nm,
|
|
215
|
+
area_m2,
|
|
216
|
+
dlambda_nm,
|
|
217
|
+
scene.temp_K,
|
|
218
|
+
lod_rad,
|
|
219
|
+
eps_warm_T_cold,
|
|
220
|
+
detector.quantum_efficiency,
|
|
221
|
+
detector.dqe,
|
|
222
|
+
core_area_lod2,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Detector noise
|
|
226
|
+
total_photon_cr = Cp + CRbs + CRbz + CRbez
|
|
227
|
+
t_photon = photon_counting_time(jnp.maximum(total_photon_cr, 1e-30), n_pix)
|
|
228
|
+
CRbd = count_rate_detector(
|
|
229
|
+
n_pix,
|
|
230
|
+
detector.dark_current_rate,
|
|
231
|
+
detector.read_noise_electrons,
|
|
232
|
+
detector.read_time,
|
|
233
|
+
detector.cic_rate,
|
|
234
|
+
t_photon,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Total background
|
|
238
|
+
Cb = CRbs + CRbz + CRbez + CRbbin + CRbth + CRbd
|
|
239
|
+
|
|
240
|
+
# Noise floor (AYO/jaxedith path)
|
|
241
|
+
CRnf_star = noise_floor_stellar(
|
|
242
|
+
scene.F0,
|
|
243
|
+
scene.Fs_over_F0,
|
|
244
|
+
area_m2,
|
|
245
|
+
throughput,
|
|
246
|
+
dlambda_nm,
|
|
247
|
+
scene.n_channels,
|
|
248
|
+
snr,
|
|
249
|
+
noisefloor_value,
|
|
250
|
+
core_area_lod2,
|
|
251
|
+
)
|
|
252
|
+
CRnf_ez = noise_floor_exozodi(CRbez, snr, scene.ez_ppf)
|
|
253
|
+
Cnf = noise_floor_total(CRnf_star, CRnf_ez, config.include_exozodi_noise_floor)
|
|
254
|
+
|
|
255
|
+
# Speckle residual (EXOSIMS path)
|
|
256
|
+
Csp = speckle_residual(CRbs, config.ppfact, config.stability_fact)
|
|
257
|
+
|
|
258
|
+
return Cp, Cb, Cnf, Csp
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# -- Public functions ----------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def calc_exptime(
|
|
265
|
+
optical_path,
|
|
266
|
+
scene,
|
|
267
|
+
wavelength_nm,
|
|
268
|
+
separation_lod,
|
|
269
|
+
dlambda_nm,
|
|
270
|
+
snr,
|
|
271
|
+
config=None,
|
|
272
|
+
eps_warm_T_cold=0.0,
|
|
273
|
+
):
|
|
274
|
+
"""Calculate exposure time for a single wavelength and separation.
|
|
275
|
+
|
|
276
|
+
This is the main entry point for the JAX ETC. All inputs are
|
|
277
|
+
``eqx.Module`` pytrees, so the full computation is JIT-traceable
|
|
278
|
+
via ``eqx.filter_jit``.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
optical_path: ``optixstuff.OpticalPath`` eqx.Module containing
|
|
282
|
+
primary, coronagraph, attenuating elements, and detector.
|
|
283
|
+
scene: :class:`ETCScene` instance with astrophysical parameters.
|
|
284
|
+
wavelength_nm: Observation wavelength [nm].
|
|
285
|
+
separation_lod: Planet separation in lam/D.
|
|
286
|
+
dlambda_nm: Bandwidth per spectral element [nm].
|
|
287
|
+
snr: Target signal-to-noise ratio.
|
|
288
|
+
config: :class:`ETCConfig` instance. Defaults to ``CONFIG``.
|
|
289
|
+
eps_warm_T_cold: Warm-optics emissivity times cold transmission.
|
|
290
|
+
Defaults to 0.0 (no thermal background from optics).
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Exposure time in seconds.
|
|
294
|
+
"""
|
|
295
|
+
if config is None:
|
|
296
|
+
config = CONFIG
|
|
297
|
+
|
|
298
|
+
Cp, Cb, Cnf, Csp = _compute_count_rates(
|
|
299
|
+
optical_path,
|
|
300
|
+
scene,
|
|
301
|
+
wavelength_nm,
|
|
302
|
+
separation_lod,
|
|
303
|
+
dlambda_nm,
|
|
304
|
+
snr,
|
|
305
|
+
config,
|
|
306
|
+
eps_warm_T_cold,
|
|
307
|
+
)
|
|
308
|
+
return _dispatch_exptime(Cp, Cb, Cnf, Csp, snr, config)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def calc_snr(
|
|
312
|
+
optical_path,
|
|
313
|
+
scene,
|
|
314
|
+
wavelength_nm,
|
|
315
|
+
separation_lod,
|
|
316
|
+
dlambda_nm,
|
|
317
|
+
t_obs,
|
|
318
|
+
config=None,
|
|
319
|
+
eps_warm_T_cold=0.0,
|
|
320
|
+
):
|
|
321
|
+
"""Calculate achieved SNR for a given observation time.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
optical_path: ``optixstuff.OpticalPath`` eqx.Module.
|
|
325
|
+
scene: :class:`ETCScene` instance with astrophysical parameters.
|
|
326
|
+
wavelength_nm: Observation wavelength [nm].
|
|
327
|
+
separation_lod: Planet separation in lam/D.
|
|
328
|
+
dlambda_nm: Bandwidth per spectral element [nm].
|
|
329
|
+
t_obs: Total observation time [s].
|
|
330
|
+
config: :class:`ETCConfig` instance. Defaults to ``CONFIG``.
|
|
331
|
+
eps_warm_T_cold: Warm-optics emissivity times cold transmission.
|
|
332
|
+
Defaults to 0.0.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Achieved signal-to-noise ratio.
|
|
336
|
+
"""
|
|
337
|
+
if config is None:
|
|
338
|
+
config = CONFIG
|
|
339
|
+
|
|
340
|
+
# For SNR-solve mode, compute noise floor rates with SNR=1
|
|
341
|
+
Cp, Cb, Cnf, Csp = _compute_count_rates(
|
|
342
|
+
optical_path,
|
|
343
|
+
scene,
|
|
344
|
+
wavelength_nm,
|
|
345
|
+
separation_lod,
|
|
346
|
+
dlambda_nm,
|
|
347
|
+
1.0,
|
|
348
|
+
config,
|
|
349
|
+
eps_warm_T_cold,
|
|
350
|
+
)
|
|
351
|
+
return _dispatch_snr(Cp, Cb, Cnf, Csp, t_obs, config)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def calc_count_rates(
|
|
355
|
+
optical_path,
|
|
356
|
+
scene,
|
|
357
|
+
wavelength_nm,
|
|
358
|
+
separation_lod,
|
|
359
|
+
dlambda_nm,
|
|
360
|
+
snr=7.0,
|
|
361
|
+
config=None,
|
|
362
|
+
eps_warm_T_cold=0.0,
|
|
363
|
+
):
|
|
364
|
+
"""Compute all count rates without solving for exposure time.
|
|
365
|
+
|
|
366
|
+
Useful for debugging and comparing individual count rates against
|
|
367
|
+
jaxedith/AYO/EXOSIMS values.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
optical_path: ``optixstuff.OpticalPath`` eqx.Module.
|
|
371
|
+
scene: :class:`ETCScene` instance.
|
|
372
|
+
wavelength_nm: Observation wavelength [nm].
|
|
373
|
+
separation_lod: Planet separation in lam/D.
|
|
374
|
+
dlambda_nm: Bandwidth per spectral element [nm].
|
|
375
|
+
snr: SNR used for noise floor calculations.
|
|
376
|
+
config: :class:`ETCConfig` instance. Defaults to ``CONFIG``.
|
|
377
|
+
eps_warm_T_cold: Warm-optics emissivity times cold transmission.
|
|
378
|
+
Defaults to 0.0.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
Tuple of (Cp, Cb, Cnf, Csp).
|
|
382
|
+
"""
|
|
383
|
+
if config is None:
|
|
384
|
+
config = CONFIG
|
|
385
|
+
|
|
386
|
+
return _compute_count_rates(
|
|
387
|
+
optical_path,
|
|
388
|
+
scene,
|
|
389
|
+
wavelength_nm,
|
|
390
|
+
separation_lod,
|
|
391
|
+
dlambda_nm,
|
|
392
|
+
snr,
|
|
393
|
+
config,
|
|
394
|
+
eps_warm_T_cold,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def calc_exptime_spectrum(
|
|
399
|
+
optical_path,
|
|
400
|
+
scene,
|
|
401
|
+
wavelength_array_nm,
|
|
402
|
+
separation_lod,
|
|
403
|
+
dlambda_array_nm,
|
|
404
|
+
snr_array,
|
|
405
|
+
config=None,
|
|
406
|
+
eps_warm_T_cold=0.0,
|
|
407
|
+
):
|
|
408
|
+
"""Vectorized exposure time calculation over wavelength bins.
|
|
409
|
+
|
|
410
|
+
Uses ``jax.vmap`` to compute exposure times independently for each
|
|
411
|
+
wavelength bin. All inputs are eqx.Module pytrees, so this function
|
|
412
|
+
is JIT-traceable via ``eqx.filter_jit``.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
optical_path: ``optixstuff.OpticalPath`` eqx.Module.
|
|
416
|
+
scene: :class:`ETCScene` instance. ``F0`` and ``Fs_over_F0`` should
|
|
417
|
+
be 1D arrays of the same length as ``wavelength_array_nm``.
|
|
418
|
+
wavelength_array_nm: Array of wavelengths [nm].
|
|
419
|
+
separation_lod: Scalar separation in lam/D (same for all bins).
|
|
420
|
+
dlambda_array_nm: Array of bandwidths [nm], one per wavelength bin.
|
|
421
|
+
snr_array: Array of target SNRs, one per wavelength bin.
|
|
422
|
+
config: :class:`ETCConfig` instance. Defaults to ``CONFIG``.
|
|
423
|
+
eps_warm_T_cold: Warm-optics emissivity times cold transmission.
|
|
424
|
+
Defaults to 0.0.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Array of exposure times in seconds.
|
|
428
|
+
|
|
429
|
+
Raises:
|
|
430
|
+
ValueError: If array inputs have mismatched shapes or are not 1D.
|
|
431
|
+
"""
|
|
432
|
+
if config is None:
|
|
433
|
+
config = CONFIG
|
|
434
|
+
|
|
435
|
+
# Convert to arrays and validate shapes (runs before tracing)
|
|
436
|
+
wavelength_array_nm = jnp.asarray(wavelength_array_nm)
|
|
437
|
+
dlambda_array_nm = jnp.asarray(dlambda_array_nm)
|
|
438
|
+
snr_array = jnp.asarray(snr_array)
|
|
439
|
+
|
|
440
|
+
if wavelength_array_nm.ndim != 1:
|
|
441
|
+
raise ValueError(
|
|
442
|
+
f"wavelength_array_nm must be 1D, got shape {wavelength_array_nm.shape}"
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
n_bins = wavelength_array_nm.shape[0]
|
|
446
|
+
|
|
447
|
+
if dlambda_array_nm.shape != (n_bins,):
|
|
448
|
+
raise ValueError(
|
|
449
|
+
f"dlambda_array_nm shape {dlambda_array_nm.shape} does not match "
|
|
450
|
+
f"wavelength_array_nm length {n_bins}"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
if snr_array.shape != (n_bins,):
|
|
454
|
+
raise ValueError(
|
|
455
|
+
f"snr_array shape {snr_array.shape} does not match "
|
|
456
|
+
f"wavelength_array_nm length {n_bins}"
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# vmap over wavelength axis -- scene parameters that vary must be 1D arrays
|
|
460
|
+
return jax.vmap(
|
|
461
|
+
lambda wl, dl, s_snr: calc_exptime(
|
|
462
|
+
optical_path,
|
|
463
|
+
scene,
|
|
464
|
+
wl,
|
|
465
|
+
separation_lod,
|
|
466
|
+
dl,
|
|
467
|
+
s_snr,
|
|
468
|
+
config,
|
|
469
|
+
eps_warm_T_cold,
|
|
470
|
+
)
|
|
471
|
+
)(wavelength_array_nm, dlambda_array_nm, snr_array)
|