Observation Planning
| Function | Description |
|---|---|
night_observability(facility, ra, dec, date) | Observability windows, Moon separation, etc. ra and dec in decimal degrees |
best_pop(facility, dec, ha, config) | Find best POP configurations for a target |
print_pop_results(facility, config, results) | Pretty-print best_pop results |
obs_plan(target, facility, ra, dec, date, pop, config) | Gantt-style observability plot |
chara_plan(target, facility, ra, dec, date, pop, config) | CHARA-plan style delay plot |
compute_delays(facility, dec, ha, config, pop) | Compute OPD delays for a given config/POP. config: 0=unused, 1=use, 2=reference cart; pop: one entry per telescope in 1:5 |
in_delay(facility, dec, ha, config, pop) | Check which baselines are within delay limits (same argument order as compute_delays) |
observable_epochs(facility, target, dates) | Filter epochs on elevation and (optionally) delay lines — opt-in, used by simulate |
moon_illumination(jd) | Fractional lunar illumination for a Julian Date |
moon_radec(jd) | Approximate lunar RA/Dec for a Julian Date |
angular_separation(ra1, dec1, ra2, dec2) | Angular separation in degrees |
OITOOLS.night_observability — Function
night_observability(facility, ra, dec, obsdate; alt_limit=30.0, alt_max=90.0,
moon_min_sep=30.0, dark_offset=0.0, step_minutes=1)Compute the observability grid for a target on a given night. Internally uses Float32 for LST, HA, alt, az arrays.
Returns a NamedTuple with fields:
utc: UTC hours vector (Float64)lst: local sidereal time, hours (Float32)ha: hour angle, hours (Float32)alt: altitude, degrees (Float32)az: azimuth, degrees (Float32)lst_midnight: LST at local midnight (Float64)good_alt: indices wherealt_limit < alt < alt_maxmoon_sep: Moon–target separation, degrees (Float32)moon_fli: fractional lunar illumination (Float64, single value for the night)good_moon: indices wheremoon_sep > moon_min_sep
Arguments:
facility: FacilityConfig (provides lat, lon)ra: right ascension in degreesdec: declination in degreesobsdate: DateTime of the observing eveningalt_limit: minimum elevation in degrees (default 30)alt_max: maximum safe elevation in degrees (default 90, e.g. 80 for CHARA)moon_min_sep: minimum Moon separation in degrees (default 30)dark_offset: hours after sunset / before sunrise for deeper twilight (default 0)step_minutes: time resolution in minutes (default 1)
OITOOLS.best_pop — Function
best_pop(facility, dec, ha, config; n_best=5, min_minutes=10, delay_length=nothing)Brute-force search over all POP combinations.
Returns a vector of NamedTuples (pop, score) sorted by score (minutes observable), keeping up to n_best results with score ≥ min_minutes.
Arguments:
config: telescope configuration (0/1/2); only telescopes with config>0 are usedn_best: number of top solutions to returnmin_minutes: discard solutions below this thresholddelay_length: override per-telescope delay limits with a uniform value (m)
OITOOLS.print_pop_results — Function
print_pop_results(facility, config, results)Pretty-print the output of best_pop.
OITOOLS.obs_plan — Function
obs_plan(targetname, facility, ra, dec, obsdate, pop, config;
alt_limit=30.0, alt_max=90.0, delay_length=nothing,
dark_offset=0.0, step_minutes=1, figsize=(10,5), savefile="")Produce a Gantt-style observability plot for one target on one night, showing dark time, altitude window, and delay feasibility.
Pass delay_length=43.0 for a conservative delay estimate. Pass savefile="path.png" to save to file and close the figure.
OITOOLS.chara_plan — Function
chara_plan(targetname, facility, ra, dec, obsdate, pop, config;
alt_limit=30.0, alt_max=90.0, delay_length=nothing,
dark_offset=0.0, step_minutes=1)Produce a delay-vs-LST plot with altitude overlay, similar to the classic chara_plan software from GSU.
Each baseline's delay cart position is plotted vs LST, with the altitude curve and elevation limit overlaid.
Pass delay_length=43.0 for a conservative delay estimate.
OITOOLS.compute_delays — Function
compute_delays(facility, dec, ha, config, pop; pop_array=CHARA_POP_ARRAY, airpath=CHARA_AIRPATH)Compute delay cart positions for all baselines over a time grid.
Returns (delay_carts, nbaselines, baseline_names, baseline_stations) where delay_carts is nbaselines × length(ha).
Arguments:
facility: FacilityConfigdec: declination in degreesha: hour angle vector (hours)config: telescope configuration vector (0=unused, 1=use, 2=reference)pop: POP assignment vector (one per telescope)
OITOOLS.in_delay — Function
in_delay(facility, dec, ha, config, pop; delay_length=nothing, kwargs...)Determine when the target is within delay-line limits for all baselines.
Returns a NamedTuple with:
delay_carts: delay cart positions (nbaselines × ntimes)has_delay: BitVector, true where all baselines are within limitsgood_delay: indices into the time grid where observing is feasiblenbaselines,baseline_names,baseline_stations
The per-telescope delay limits come from facility.delay_lengths. Pass delay_length=43.0 to override with a uniform (conservative) value.
OITOOLS.moon_illumination — Function
moon_illumination(jd)Fractional lunar illumination (0–1) for a given Julian Date.
OITOOLS.moon_radec — Function
moon_radec(jd)Low-precision Moon RA/Dec (degrees) for a given Julian Date. Accuracy ~1° in position — sufficient for separation checks.
OITOOLS.angular_separation — Function
angular_separation(ra1, dec1, ra2, dec2)Angular separation in degrees between two positions (all in degrees).
OITOOLS.index_runs — Function
index_runs(idx) -> Vector{Tuple{Int,Int}}Split a sorted index vector into maximal runs of consecutive integers.
An observability window is not necessarily one block: a target can dip below the elevation limit and come back, and a baseline routinely leaves and re-enters the delay range, which is the normal shape of a POP configuration. Drawing such a set as a single bar from idx[1] to idx[end] – which is what this file used to do – paints the gaps as observable.
Simulation
| Function | Description |
|---|---|
simulate(facility, target, combiner, wave, dates, outfile) | Simulate observations from array geometry |
simulate_from_oifits(infile, outfile) | Simulate using UV coverage of an existing OIFITS file |
get_uv(l, h, λ, δ, baselines) | UV coordinates from latitude l (rad), hour angles h (rad, row vector), wavelengths λ (m), declination δ (rad) and a 3×n baseline matrix (m). Returns (nuv, uv, u_M, v_M, w_M); uv is in cycles/rad |
read_facility_file(file) | Read facility configuration (TOML) |
read_obs_file(file) | Read target configuration (TOML) |
read_comb_file(file) | Read combiner configuration (TOML) |
read_wave_file(file) | Read wavelength configuration (TOML) |
list_configs([dir]) | Enumerate available facility / combiner / wavelength / target configs, classified by content, plus the wavelength→combiner map |
predict_errors(facility, combiner, wavelength; mag, visamp, elevation_deg) | Predicted per-channel σ(V²), σ(CP), σ(visamp), σ(visphi), Strehl, DIT and photon counts — the noise model without simulating to a file |
observable_epochs(facility, target, dates; min_elevation, pops) | Opt-in observability filter for simulate |
strehl_ratio(λ, D, seeing, t0, mag_ao, ao) | AO Strehl ratio (port of JMMC Band.strehl) |
coupling_efficiency(λ, D, seeing, t0, mag_ao, ao) | Strehl, or seeing-limited coupling when ao === nothing |
fried_parameter(seeing, λ; elevation_deg) | Fried parameter r₀ at a wavelength and airmass |
seeing_from_r0(r0) | Seeing in arcsec from a Fried parameter |
atm_transmission(λ) | Atmospheric transmission hook (1.0 by default) |
band_for_wavelength(λ) / band_by_name(name) | Photometric band lookup |
zero_point_flux(λ) | Zero-magnitude photon flux, ph/s/m²/µm |
airmass(alt_deg) | Plane-parallel airmass from altitude |
datetime_to_jd(dt) / datetime_to_mjd(dt) | Julian and Modified Julian Date |
gantt_onenight(targetname, obsdate, lst, lst_midnight, az, alt, good_alt, good_delay; good_twilight, figsize, savefile, show_alt) | Gantt chart from precomputed night_observability output. Usually reached through obs_plan |
sunrise_sunset(obsdate, latitude, longitude; zenith=102.0) | Rise/set times in decimal UT hours. zenith in degrees: 90°50′ sunrise/sunset, 96° civil, 102° nautical (default), 108° astronomical twilight |
alt_az(dec_deg, lat_deg, ha_hours) | Altitude/azimuth in degrees. Note the argument order: declination first, then latitude, then hour angle in hours |
opd_limits(base, alt, az) | Projected optical path for a baseline vector. alt/az in radians — note alt_az returns degrees, so they do not compose directly |
| Type | Description |
|---|---|
FacilityConfig | Array facility configuration (telescopes, positions, atmosphere) |
TargetConfig | Target configuration (coordinates, proper motion) |
CombinerConfig | Beam combiner configuration (throughput, noise, calibration) |
WaveConfig | Wavelength/spectral configuration |
OITOOLS.observable_epochs — Function
observable_epochs(facility, target, dates; min_elevation=nothing, max_elevation=nothing,
pops=nothing, config=Int[], delay_length=nothing)Select the epochs in dates at which target is actually observable from facility.
This is opt-in and composable: simulate() does not call it unless you ask. A simulation made to test image reconstruction usually wants the full uv coverage regardless of whether a real night could deliver it, so every constraint here is off by default and only switches on when you pass the corresponding keyword.
min_elevation,max_elevation: degrees.nothing(default) applies no elevation cut.pops: a POP configuration, one entry per telescope, values in1:5.nothing(default) applies no delay-line check at all. POPs are never chosen for you — runbest_popfirst if you want a recommendation, then pass the result here.config: telescope-use flags for the delay check (1use,0skip,2reference cart); defaults to using every telescope, i.e. allntel*(ntel-1)/2baselines must fit.delay_length: override the per-telescope delay-line lengths with one uniform value (m).
target.raep0 / target.decep0 are in degrees, as elsewhere in the OIFITS-facing API.
Returns (dates, mask, report):
dates: the surviving epochs, in input ordermask::BitVector: true where the epoch survivedreport:(n_in, n_out, n_dropped_elevation, n_dropped_delay, elevation), whereelevationis the altitude in degrees at every input epoch.
dates_ok, mask, rep = observable_epochs(facility, target, dates;
min_elevation = 30.0,
pops = [1,3,5,2,4,1])
simulate(facility, target, combiner, wavelength, dates_ok, "sim.oifits"; flat_model=m)OITOOLS.AOConfig — Type
AOConfigAdaptive-optics parameters for a facility's telescopes, consumed by strehl_ratio.
Defaults are ASPRO 2's AO_CHARA setup as of the CHARA 2026A configuration.
band: wavefront-sensor band. "V" is special-cased to a 0.5 um reference wavelength.n_subpupils: subapertures, sets the WFS photon collecting area.n_actuators: corrected modes, sets the fitting error. Falls back ton_subpupilsif 0.dit: WFS integration time [s].ron: WFS detector read noise [e-].qe: WFS quantum efficiency.transmission: fraction of starlight diverted to the WFS, i.e. lost to the science channel. 0 means the WFS is fed by a separate path.mag_offset: added to the guide-star magnitude before computing WFS flux.strehl_max: Strehl ceiling. 0 does not mean "uncapped" — it selects the per-band default fromPHOTOMETRIC_BANDS, matching ASPRO.
OITOOLS.CombinerConfig — Type
CombinerConfigBeam-combiner properties. The fields mirror ASPRO 2's FocalInstrumentSetup one for one, so that a combiner config can be transcribed directly from ASPRO's interferometer XML (aspro-conf, e.g. CHARA.xml) and reproduce its predictions.
transmission: end-to-end transmission, array and instrument, excluding quantum efficiency, Strehl and atmosphere (ASPROtransmission). Because it already covers the array,FacilityConfig.throughputis 1.0 for any facility whose combiners are specified this way.instrument_visibility: instrumental contrast (ASPROinstrumentVisibility).dit: detector integration time in seconds — a fixed instrument property, shortened only when the detector would saturate (ASPROdit).total_int_time: total on-source time per uv point, seconds (ASPROdefaultTotalIntegrationTime).detector_saturation: full-well in electrons per pixel (ASPROdetectorSaturation).flux_frac_fringes/flux_frac_photometry: how the light splits between the interferometric and photometric channels (ASPROfracFluxIn…).vis_cal_err: systematic error on|V|, as a fraction. ASPRO stores this asinstrumentVisibilityBiasin percent, soinstrumentVisibilityBias = 1isvis_cal_err = 0.01. The systematic onV²is twice this.phase_cal_err: systematic error on phases and closure phases, in degrees (ASPROinstrumentPhaseBias).
OITOOLS.strehl_ratio — Function
strehl_ratio(λ, D, seeing, t0, mag_ao, ao::AOConfig; elevation_deg=90.0)Strehl ratio at wavelength λ (metres) for a telescope of diameter D (metres), given seeing (arcsec at 500 nm), atmospheric coherence time t0 (seconds), and adaptive optics guiding on a star of magnitude mag_ao in the WFS band.
Port of jmal's Band.strehl. Five variance terms are summed and the result is the sum of a diffraction-limited core and a seeing-limited halo:
S = exp(-σ²) + (1 - exp(-σ²)) / (1 + (D/r0_λ)²)The halo is why the Strehl does not collapse to zero on faint guide stars: as σ² → ∞ the Strehl tends to 1/(1 + (D/r0_λ)²), the relative peak intensity of an uncorrected long-exposure PSF. Reproduces ASPRO's CHARA curves to a median 0.7% (worst case 3.1% in H/K).
Two details that are easy to get wrong and are deliberate here: the servo term uses t0 unscaled — neither shifted to the science wavelength nor corrected for airmass — and the subaperture pitch is sqrt(π(D/2)²/n_actuators), not D/sqrt(n_actuators).
OITOOLS.coupling_efficiency — Function
coupling_efficiency(λ, D, seeing, t0, mag_ao, ao; elevation_deg=90.0)Fraction of the light collected by one telescope that reaches the combiner, i.e. the Strehl ratio when an AOConfig is available and the seeing-limited estimate min(1,(r0/D)²) when ao === nothing.
The fallback is what this package used unconditionally before the AO model existed. It is appropriate only for an uncorrected aperture and underestimates a modern AO-equipped array by roughly 5x in H and 20x in R at CHARA, so facilities that have AO should carry an [ao] block in their config.
OITOOLS.fried_parameter — Function
fried_parameter(seeing, λ; elevation_deg=90.0)Fried parameter r0 in metres at wavelength λ (metres) for a given seeing (arcsec, defined at 500 nm), scaled to the line of sight at elevation_deg.
r0 ∝ λ^(6/5) · airmass^(-3/5).
OITOOLS.seeing_from_r0 — Function
seeing_from_r0(r0)Seeing in arcsec (at 500 nm) corresponding to a Fried parameter r0 in metres, using the same constant as fried_parameter.
OITOOLS.atm_transmission — Function
atm_transmission(λ)Fraction of light transmitted by the atmosphere at wavelength λ (metres). Defaults to 1.0 at every wavelength.
ASPRO convolves an ESO SkyCalc table (skytable_mean_atm.fits, computed for Paranal) over each spectral channel. That table is not redistributable and hand-rolling telluric bands would be worse than saying nothing, so this is a hook: pass a transmission column in a wavelength config, or redefine this, to model tellurics. Bands to care about at CHARA are O2 A at 760 nm and H2O at 820-950 nm (SPICA), and H2O at 1350-1420 nm (MIRC-X J).
OITOOLS.PhotometricBand — Type
PhotometricBandOne photometric band: its name, central wavelength and width in metres, the photon flux of a zero-magnitude star (f0, photons/s/m²/m), and the default Strehl ceiling jmal applies in that band. See PHOTOMETRIC_BANDS.
OITOOLS.PHOTOMETRIC_BANDS — Constant
PHOTOMETRIC_BANDSThe photometric bands known to OITOOLS, from U to Q, as PhotometricBand entries.
Zero points are derived from the Bessell et al. (1998) / Cohen et al. (2003) f_lambda values for a 0-magnitude star, converted to photon flux. The V entry is the textbook "a zeroth-magnitude star delivers ~1000 photons/s/cm²/Å".
OITOOLS.band_for_wavelength — Function
band_for_wavelength(λ)The PhotometricBand whose central wavelength is closest to λ (in metres).
OITOOLS.band_by_name — Function
band_by_name(name)Look up a PhotometricBand by name (case-insensitive), or nothing.
OITOOLS.zero_point_flux — Function
zero_point_flux(λ)Photon flux of a zero-magnitude star at wavelength λ (metres), in photons/s/m²/μm.
Interpolated log-linearly in log(λ) between the band centres of PHOTOMETRIC_BANDS, and clamped to the V and Q endpoints outside that range.
OITOOLS.airmass — Function
airmass(alt_deg)Plane-parallel airmass 1/cos(z) from altitude in degrees. The altitude is clamped to ≥ 0.5° so the value stays finite at and below the horizon, matching how ASPRO guards its Strehl computation.
OITOOLS.datetime_to_jd — Function
datetime_to_jd(dt::DateTime)Convert a Julia DateTime to Julian Date.
OITOOLS.datetime_to_mjd — Function
datetime_to_mjd(dt::DateTime)Convert a Julia DateTime to Modified Julian Date (JD - 2400000.5), the epoch OIFITS uses.
OITOOLS.read_facility_file — Function
read_facility_file(path) -> FacilityConfigRead an interferometric array: site coordinates, telescopes, station positions, delay-line lengths, seeing and AO parameters.
path may be a bare name ("CHARA"), a name with extension, or a full path. Bare names are looked up in OITOOLS' own src/configs directory, so the shipped configs work without a path; see list_configs for what is available. A .toml file is read with the current schema, anything else with the legacy whitespace-delimited reader.
OITOOLS.read_obs_file — Function
read_obs_file(path) -> TargetConfigRead a target definition (name, RA/Dec in degrees, proper motion, spectral type) — the OI_TARGET header simulate writes.
path may be a bare name ("CHARA"), a name with extension, or a full path. Bare names are looked up in OITOOLS' own src/configs directory, so the shipped configs work without a path; see list_configs for what is available. A .toml file is read with the current schema, anything else with the legacy whitespace-delimited reader.
OITOOLS.read_comb_file — Function
read_comb_file(path) -> CombinerConfigRead a beam combiner: transmission, detector properties, integration time and the calibration error floors used by the noise model.
path may be a bare name ("CHARA"), a name with extension, or a full path. Bare names are looked up in OITOOLS' own src/configs directory, so the shipped configs work without a path; see list_configs for what is available. A .toml file is read with the current schema, anything else with the legacy whitespace-delimited reader.
OITOOLS.read_wave_file — Function
read_wave_file(path) -> WaveConfigRead a spectral setup: per-channel wavelengths and bandwidths, plus the name of the combiner it belongs to.
path may be a bare name ("CHARA"), a name with extension, or a full path. Bare names are looked up in OITOOLS' own src/configs directory, so the shipped configs work without a path; see list_configs for what is available. A .toml file is read with the current schema, anything else with the legacy whitespace-delimited reader.
OITOOLS.list_configs — Function
list_configs([dir]) -> (; facilities, combiners, wavelengths, targets, unknown, wave_combiner)Enumerate the configuration files that read_facility_file, read_comb_file, read_wave_file and read_obs_file can load, classified by what they actually contain. dir defaults to the configs shipped with OITOOLS.
Nothing about a filename says what kind of config it is, so the classification reads each file: a facility has a site (lat), a wavelength setup has lambda and names its combiner, a target has raep0, and anything else carrying transmission/read_noise is a combiner. Files that parse but match nothing land in unknown, and so do files that fail to parse — enumerating a directory should never throw.
wave_combiner maps each wavelength setup to the combiner it belongs to. That link exists only inside the file, so it is the only way to offer, say, MIRC-X's spectral modes without hard-coding them.
julia> c = list_configs();
julia> c.facilities
5-element Vector{String}:
"CHARA"
"VLTI_AT_large"
"VLTI_AT_medium"
"VLTI_AT_small"
"VLTI_UT"
julia> c.wave_combiner["MIRCX_LOWH"]
"MIRCX"OITOOLS.predict_errors — Function
predict_errors(facility, combiner, wavelength; mag, visamp=1.0, elevation_deg=90.0,
mag_ao=nothing, channel=nothing) -> NamedTuplePredicted per-channel uncertainties for one setup, without running a simulation or writing a file.
simulate returns the result of writing an OIFITS and keeps nothing in memory, so the only way to ask "what SNR would I get?" used to be to simulate to disk and read it back. This runs the same noise model directly.
Returns, one entry per spectral channel of wavelength:
| field | meaning |
|---|---|
λ, δλ | channel centre and width, m |
strehl | coupling efficiency actually used |
dit | integration time per frame, s (possibly shortened for saturation) |
nframes | frames coadded |
nphot | photons per telescope per DIT, fringe channel |
sigma_v2 | σ(V²), including the vis_cal_err systematic |
sigma_cp | σ(closure phase), degrees, including phase_cal_err |
sigma_visamp | σ of the visibility amplitude |
sigma_visphi | σ(visibility phase), degrees |
mag follows the same rules as simulate: a scalar, a Dict of band ⇒ magnitude, or one value per channel. visamp is the visibility amplitude at which to evaluate the errors — errors depend on it, so the default of 1.0 gives the unresolved (best) case.
Mirrors simulate's internals exactly; see demos/validate_noise_model.jl, which checks this model against ASPRO 2's published curves.