# Calibration Loads The HOT/COLD load system provides the absolute flux reference for the heterodyne calibration. This page describes how raw load data is processed into the gain calibration factor $\gamma$, receiver temperature $T_{rec}$, and bad channel mask. ## Load Extraction Calibration subscans are identified by their `sobsmode` string: - **HOT** — hot load (ambient temperature blackbody) - **COLD** / **COL** — cold load (typically 77 K LN₂) - **SKY** — sky measurement - **ZERO** — zero-level check For each calibration subscan, the count data `[C, D]` is averaged over the dump axis (NaN-aware, skipping padded values) to produce mean hot/cold counts per channel. **Implementation:** `cal-core/src/math/dump_mean.rs` → `nan_mean_axis()` ## Effective Load Temperature The hot load is at ambient temperature, but spillover past the load couples to the ambient environment. The effective load temperature corrects for this: $$ T_{hot,eff}^{sig} = \frac{T_{RJ}(\nu_{sig}, T_{hot}) - f_{amb} \cdot T_{RJ}(\nu_{sig}, T_{amb})}{1 - f_{amb}} $$ where $f_{amb} = 1 - f_{eff}$ is the ambient coupling fraction and $f_{eff}$ is the forward efficiency. The sky coupling coefficient relates ambient to effective temperature: $$ a_{sig} = \frac{T_{RJ}(\nu_{sig}, T_{amb})}{T_{hot,eff}^{sig}} $$ **Implementation:** `cal-io/src/resolve.rs` ## Gamma (Gain Calibration Factor) The gamma factor converts count differences to temperature differences: $$ \gamma(\nu) = \frac{C_{hot}(\nu) - C_{cold}(\nu)}{T'_{hot}(\nu) - T'_{cold}(\nu)} \cdot (g_s x_s + g_i x_i) $$ This is the sensitivity in counts per Kelvin, including the sideband gain weighting. **Implementation:** `cal-core/src/math/gamma.rs` → `gain_calibration_formula()` ## Receiver Temperature The Y-factor method yields the primed receiver temperature: $$ y = \frac{C_{hot}}{C_{cold}} $$ $$ T'_{rec} = \frac{T'_{hot} - y \cdot T'_{cold}}{y - 1} $$ Converting to single-sideband: $$ T_{rec,SSB} = \left(T'_{rec} - T'_{term}\right) \cdot \frac{g_s x_s + g_i x_i}{g_s x_s} $$ where $T'_{term}$ is the sideband-weighted termination temperature (spillover). **Implementation:** `cal-core/src/math/temperature.rs` → `t_rec_formula()` ## Bad Channel Detection A channel is flagged as bad if any of the following conditions hold: 1. $C_{hot} \leq C_{cold}$ — load signal not detected 2. $(C_{hot} - C_{cold}) < \text{clip\_counts} \cdot \max(\text{smoothed}(C_{hot} - C_{cold}))$ — weak relative signal 3. $T_{rec,SSB} \leq 0$ — unphysical receiver temperature 4. $T_{rec,SSB} > \text{clip\_tsys} \cdot \frac{h \nu_{typ}}{k_B}$ — unrealistically high The `clip_tsys` (default 200 K) and `clip_counts` (default 0.01) thresholds are configurable. **Implementation:** `cal-core/src/math/temperature.rs` → `compute_bad_channels()` ## Per-Channel Load Temperature Tables Some receivers provide per-channel RJ load temperatures via the `LOAD_TEMP_ARRAY` (pre-computed from load emission models). When available, these replace the scalar $T_{RJ}(T_{hot})$ with per-channel values, improving accuracy near band edges where the load is not perfectly isothermal. **Implementation:** `cal-core/src/scan/cal_load.rs` -- `CalibrationLoad::new_with_load_temps()` ## Gain Drift Correction (Gain Interpolation) When `--gain-interpolate` is active, raw counts are corrected for receiver gain and noise-temperature drift between the two bracketing HOT/COLD calibration epochs: $$ C_{corr}(\nu) = \frac{C(\nu)}{1 + w \cdot \Gamma(\nu)} - G_1(\nu) \, R_1(\nu) \, w \, \rho(\nu) $$ where $\Gamma$ is the fractional gain change between epochs, $\rho$ the receiver-temperature drift coefficient, and $w$ the time weight. **Time base.** The anchors $t_{start}/t_{end}$ are the MJDs of the bracketing COLD subscans — the same instants legacy kalibrate uses (its `gain_monitor_start/end_time` are the FITS `DATE` of those COLD subscans, `buffers.cpp:1397-1398`). The weight $w = (t_{subscan} - t_{start})/(t_{end} - t_{start})$ is computed once per subscan. Legacy refines $t$ per dump when `OBSMODE` is `OTFT`/`OTFSWA`/`OTFSWB`; this refinement is deliberately dropped. It is exactly equivalent for `OBSMODE`-empty data (the entire SOFIA parity corpus) and bounded below ~1 mK otherwise — revisit via issue [#24](https://github.com/ccatobs/calibrate/issues/24) if `OBSMODE`-set data enters the pipeline. **Implementation:** `cal-core/src/gain.rs` → `compute_gain_coefficients()`, `apply_gain_correction()` ## Data Flow ```{eval-rst} .. mermaid:: graph LR CS[CalibrationSnapshot
raw HOT/COLD] -->|extract & average| HC[hot_counts, cold_counts
per channel] HC --> G[gamma] HC --> TR[T_rec_prime, T_rec_SSB] HC --> BC[bad_channels mask] G --> CL[CalibrationLoad
all derived quantities] TR --> CL BC --> CL ```