Observation Planning

FunctionDescription
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_observabilityFunction
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 where alt_limit < alt < alt_max
  • moon_sep: Moon–target separation, degrees (Float32)
  • moon_fli: fractional lunar illumination (Float64, single value for the night)
  • good_moon: indices where moon_sep > moon_min_sep

Arguments:

  • facility: FacilityConfig (provides lat, lon)
  • ra: right ascension in degrees
  • dec: declination in degrees
  • obsdate: DateTime of the observing evening
  • alt_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)
source
OITOOLS.best_popFunction
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 used
  • n_best: number of top solutions to return
  • min_minutes: discard solutions below this threshold
  • delay_length: override per-telescope delay limits with a uniform value (m)
source
OITOOLS.obs_planFunction
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.

source
OITOOLS.chara_planFunction
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.

source
OITOOLS.compute_delaysFunction
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: FacilityConfig
  • dec: declination in degrees
  • ha: hour angle vector (hours)
  • config: telescope configuration vector (0=unused, 1=use, 2=reference)
  • pop: POP assignment vector (one per telescope)
source
OITOOLS.in_delayFunction
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 limits
  • good_delay: indices into the time grid where observing is feasible
  • nbaselines, 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.

source
OITOOLS.moon_radecFunction
moon_radec(jd)

Low-precision Moon RA/Dec (degrees) for a given Julian Date. Accuracy ~1° in position — sufficient for separation checks.

source
OITOOLS.index_runsFunction
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.

source

Simulation

FunctionDescription
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
TypeDescription
FacilityConfigArray facility configuration (telescopes, positions, atmosphere)
TargetConfigTarget configuration (coordinates, proper motion)
CombinerConfigBeam combiner configuration (throughput, noise, calibration)
WaveConfigWavelength/spectral configuration
OITOOLS.observable_epochsFunction
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 in 1:5. nothing (default) applies no delay-line check at all. POPs are never chosen for you — run best_pop first if you want a recommendation, then pass the result here.
  • config: telescope-use flags for the delay check (1 use, 0 skip, 2 reference cart); defaults to using every telescope, i.e. all ntel*(ntel-1)/2 baselines 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 order
  • mask::BitVector: true where the epoch survived
  • report: (n_in, n_out, n_dropped_elevation, n_dropped_delay, elevation), where elevation is 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)
source
OITOOLS.AOConfigType
AOConfig

Adaptive-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 to n_subpupils if 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 from PHOTOMETRIC_BANDS, matching ASPRO.
source
OITOOLS.CombinerConfigType
CombinerConfig

Beam-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 (ASPRO transmission). Because it already covers the array, FacilityConfig.throughput is 1.0 for any facility whose combiners are specified this way.
  • instrument_visibility: instrumental contrast (ASPRO instrumentVisibility).
  • dit: detector integration time in seconds — a fixed instrument property, shortened only when the detector would saturate (ASPRO dit).
  • total_int_time: total on-source time per uv point, seconds (ASPRO defaultTotalIntegrationTime).
  • detector_saturation: full-well in electrons per pixel (ASPRO detectorSaturation).
  • flux_frac_fringes / flux_frac_photometry: how the light splits between the interferometric and photometric channels (ASPRO fracFluxIn…).
  • vis_cal_err: systematic error on |V|, as a fraction. ASPRO stores this as instrumentVisibilityBias in percent, so instrumentVisibilityBias = 1 is vis_cal_err = 0.01. The systematic on is twice this.
  • phase_cal_err: systematic error on phases and closure phases, in degrees (ASPRO instrumentPhaseBias).
source
OITOOLS.strehl_ratioFunction
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).

source
OITOOLS.coupling_efficiencyFunction
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.

source
OITOOLS.fried_parameterFunction
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).

source
OITOOLS.atm_transmissionFunction
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).

source
OITOOLS.PhotometricBandType
PhotometricBand

One 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.

source
OITOOLS.PHOTOMETRIC_BANDSConstant
PHOTOMETRIC_BANDS

The 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²/Å".

source
OITOOLS.zero_point_fluxFunction
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.

source
OITOOLS.airmassFunction
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.

source
OITOOLS.datetime_to_mjdFunction
datetime_to_mjd(dt::DateTime)

Convert a Julia DateTime to Modified Julian Date (JD - 2400000.5), the epoch OIFITS uses.

source
OITOOLS.read_facility_fileFunction
read_facility_file(path) -> FacilityConfig

Read 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.

source
OITOOLS.read_obs_fileFunction
read_obs_file(path) -> TargetConfig

Read 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.

source
OITOOLS.read_comb_fileFunction
read_comb_file(path) -> CombinerConfig

Read 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.

source
OITOOLS.read_wave_fileFunction
read_wave_file(path) -> WaveConfig

Read 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.

source
OITOOLS.list_configsFunction
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"
source
OITOOLS.predict_errorsFunction
predict_errors(facility, combiner, wavelength; mag, visamp=1.0, elevation_deg=90.0,
               mag_ao=nothing, channel=nothing) -> NamedTuple

Predicted 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:

fieldmeaning
λ, δλchannel centre and width, m
strehlcoupling efficiency actually used
ditintegration time per frame, s (possibly shortened for saturation)
nframesframes coadded
nphotphotons 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.

source