Source code for optmat.gas.crosschecks

# SPDX-FileCopyrightText: 2026 John C. Travers and contributors
#
# SPDX-License-Identifier: MIT

"""Test the recommended models against dispersion data that no single fit uses.

Two kinds of measurement constrain dispersion without giving a refractive index:

- **Phase-matching density ratios** (Shelton 1986, 1990): two gases give the same
  electric-field-induced second-harmonic wave-vector mismatch at densities in the ratio
  ``rho_gas / rho_ref = Delta alpha_ref / Delta alpha_gas`` with
  ``Delta alpha = alpha(2 omega) - alpha(omega)`` (to first order in density). Each
  ratio ties the near-infrared dispersion of two gases together to about 0.1 %, which is
  a direct test of the neon, krypton and xenon models where they have no near-infrared
  data of their own. The fits use most of them (TC-96); here every one is compared.
- **Group-delay dispersion** (Borzsonyi et al. 2008): ``beta_2 = (lambda^3 / 2 pi c^2)
  d^2 n / d lambda^2`` per metre at the measured state.

This module predicts each from the recommended models (``models.registry``) and returns
the comparison.
"""

from dataclasses import dataclass

import numpy as np

from optmat.constants import C
from optmat.datasets import datasets
from optmat.exceptions import OptmatError
from optmat.gas.models.registry import default_model

__all__ = ["Comparison", "group_delay_dispersions", "phase_match_ratios"]


[docs] @dataclass(frozen=True) class Comparison: """A measured value against the models' prediction. Attributes ---------- key : str The dataset. row : str The row within it. wavelength : float The (fundamental) vacuum wavelength, m. measured : float The measured value. sigma : float Its one-sigma uncertainty (NaN where none is stated). predicted : float The models' prediction. """ key: str row: str wavelength: float measured: float sigma: float predicted: float @property def deviation(self) -> float: """The relative deviation of the prediction from the measurement. Returns ------- float ``predicted / measured - 1``. """ return self.predicted / self.measured - 1
[docs] def phase_match_ratios() -> list[Comparison]: """Compare every curated phase-matching density ratio with the models. Returns ------- list of Comparison One per row of every ``phase_match_density_ratio`` dataset whose two gases have recommended models; the prediction is the ratio of the models' polarizability differences between the fundamental and its second harmonic. """ comparisons = [] for dataset in datasets(quantity="phase_match_density_ratio"): if dataset.use not in {"primary", "check"}: continue try: gas_model = default_model(dataset.material) reference_model = default_model( str(dataset.parameters["reference_material"]) ) except OptmatError: continue harmonic = int(dataset.parameters.get("harmonic", 2)) # pyright: ignore[reportArgumentType] wavelength = dataset.vacuum_wavelength() sigma = dataset.standard_uncertainty() for i, row in enumerate(dataset.rows): points = np.array([wavelength[i], wavelength[i] / harmonic]) gas_alpha = gas_model.polarizability(points, warn=False) reference_alpha = reference_model.polarizability(points, warn=False) predicted = (reference_alpha[1] - reference_alpha[0]) / ( gas_alpha[1] - gas_alpha[0] ) comparisons.append( Comparison( key=dataset.key, row=row, wavelength=float(wavelength[i]), measured=float(dataset.scaled_values()[i]), sigma=float(sigma[i]), predicted=float(predicted), ) ) return comparisons
[docs] def group_delay_dispersions() -> list[Comparison]: """Compare every curated group-delay dispersion with the models. Returns ------- list of Comparison One per row of every ``group_delay_dispersion`` dataset (fs^2/m at the dataset's state), predicted by a centred second difference of the recommended model's ``n`` in wavelength. """ comparisons = [] for dataset in datasets(quantity="group_delay_dispersion"): try: model = default_model(dataset.material) except OptmatError: continue if dataset.state is None: # pragma: no cover - the format requires a state continue wavelength = dataset.vacuum_wavelength() sigma = dataset.standard_uncertainty() for i, row in enumerate(dataset.rows): lam = float(wavelength[i]) step = lam * 1e-3 n = model.refractivity( np.array([lam - step, lam, lam + step]), dataset.state, warn=False ) second = (n[0] - 2 * n[1] + n[2]) / step**2 predicted = lam**3 / (2 * np.pi * C**2) * second * 1e30 # fs^2/m comparisons.append( Comparison( key=dataset.key, row=row, wavelength=lam, measured=float(dataset.scaled_values()[i]), sigma=float(sigma[i]), predicted=float(predicted), ) ) return comparisons