Cavity & Polariton Spectroscopy

Fabry-Pérot cavity and polariton analysis for light–matter strong coupling experiments (vibrational strong coupling, exciton polaritons), covering the chain from raw transmittance data to Rabi splitting and Hopfield coefficients:

  1. Multi-oscillator Lorentz dielectric function (via CurveFitModels.jl)
  2. Complex refractive index $n$, $k$ from the dielectric function
  3. Fabry-Pérot Airy transmittance with an absorbing intracavity medium
  4. Coupled oscillator polariton model: branches, eigenvalues, mixing fractions
  5. Nonlinear least-squares fitting (via CurveFit.jl) of spectra and dispersion

Quick start

Polariton physics

using OpticalSpectroscopy

# Cavity photon energy vs incidence angle (radians)
E_cav = cavity_mode_energy([2040.0, 1.5], deg2rad.(0:5:30))

# Upper and lower polariton branches (2-level coupled oscillator model)
LP, UP = polariton_branches(E_cav, 2055.0, 25.0)

# Light-matter mixing fractions
h = hopfield_coefficients(E_cav, 2055.0, 25.0)
h.photon_LP   # photon fraction of the lower polariton at each angle

Fitting a cavity transmission spectrum

result = fit_cavity_spectrum(nu, T;
    oscillators = [(nu0 = 2055.0, Gamma = 23.0)],
    L = 12.0e-4,      # cavity length (cm)
    n_bg = 1.4)       # background refractive index

result.R                  # fitted mirror reflectance
result.polariton_peaks    # auto-extracted peak positions
predict(result)           # fitted curve on the data grid

Fitting polariton dispersion

result = fit_dispersion(angles, lp_positions, up_positions;
    molecular_modes = 2055.0)

result.rabi_splitting     # Rabi splitting (with result.rabi_err)
result.hopfield_zero      # mixing fractions at zero detuning

Conventions

  • Wavenumber units (cm⁻¹) throughout; angles in radians.
  • Model functions follow the fn(p, x) signature and are ForwardDiff-compatible, so they can be used directly with CurveFit.jl.
  • Hopfield convention: with detuning $\delta = E_{cav} - E_{vib}$ and $\theta = \tfrac{1}{2}\,\mathrm{atan2}(\Omega, \delta)$, the photon fraction of the lower polariton is $\sin^2\theta = \tfrac{1}{2}(1 - \delta/\sqrt{\delta^2 + \Omega^2})$ — at far positive detuning the LP converges to the bare vibration (matter-like). Verified against direct Hamiltonian diagonalization in the test suite.

Types

OpticalSpectroscopy.CavityFitResultType
CavityFitResult

Result from fitting a single cavity transmission spectrum.

Fields

  • R: Mirror reflectivity
  • L: Cavity length (cm)
  • n_bg: Background refractive index
  • phi: Phase shift
  • scale: Scale factor applied to transmittance
  • offset: Baseline offset
  • oscillators: Vector of NamedTuples (nu0, Gamma, A) for each oscillator
  • polariton_peaks: Vector of peak positions (cm^-1) extracted from fit
  • rsquared: R^2 goodness of fit
  • _nu: Wavenumber array used in fit (internal)
  • _T_data: Transmittance data used in fit (internal)
  • _sol: CurveFit solution object (internal)
source
OpticalSpectroscopy.DispersionFitResultType
DispersionFitResult

Result from fitting the coupled oscillator model to polariton dispersion data.

Fields

  • rabi_splitting: Rabi splitting Omega (cm^-1)
  • molecular_modes: Vector of molecular mode energies (cm^-1)
  • n_eff: Effective refractive index
  • E0: Normal-incidence cavity energy (cm^-1)
  • rabi_err: Uncertainty in Rabi splitting
  • n_eff_err: Uncertainty in n_eff
  • E0_err: Uncertainty in E0
  • lp_angles: Incidence angles for LP data (radians)
  • lp_positions: Lower polariton positions at each LP angle
  • up_angles: Incidence angles for UP data (radians)
  • up_positions: Upper polariton positions at each UP angle
  • hopfield_zero: Hopfield coefficients at zero detuning
  • rsquared: R^2 goodness of fit
  • _sol: CurveFit solution object (internal)
source

Physics

OpticalSpectroscopy.cavity_transmittanceFunction
cavity_transmittance(p, ν)

Fabry-Perot cavity transmittance with an absorbing medium as a function of frequency.

Arguments

  • p: Parameters [n, α, L, R, ϕ]
    • n: Refractive index
    • α: Absorption coefficient
    • L: Cavity length
    • R: Mirror reflectance (T = 1 - R assumed)
    • ϕ: Phase shift upon reflection
  • ν: Frequency (independent variable)

\[\begin{aligned} T(\nu) = \frac{(1-R)^2 e^{-\alpha L}}{1 + R^2 e^{-2\alpha L} - 2R e^{-\alpha L} \cos(4\pi n L \nu + 2\phi)} \end{aligned}\]

https://en.wikipedia.org/wiki/Fabry%E2%80%93P%C3%A9rot_interferometer

source
OpticalSpectroscopy.compute_cavity_transmittanceFunction
compute_cavity_transmittance(nu::Number, nu0s, Gammas, As, R, L, n_bg, phi)

Compute cavity transmittance at a single frequency for multiple Lorentzian oscillators.

Builds the full physics chain:

  1. Sum Lorentz oscillator dielectric contributions
  2. Compute complex refractive index (n, k) from dielectric function (refractive_index, extinction_coeff)
  3. Compute absorption coefficient alpha = 4pi * k * nu
  4. Feed into the Fabry-Perot Airy function (cavity_transmittance)

Arguments

  • nu: Frequency (cm^-1)
  • nu0s: Vector of oscillator center frequencies (cm^-1)
  • Gammas: Vector of oscillator linewidths (cm^-1)
  • As: Vector of oscillator amplitudes/strengths (cm^-2)
  • R: Mirror reflectivity
  • L: Cavity length (cm)
  • n_bg: Background refractive index
  • phi: Phase shift upon reflection

ForwardDiff-compatible.

source
compute_cavity_transmittance(nus::AbstractArray, nu0s, Gammas, As, R, L, n_bg, phi)

Array dispatch: compute cavity transmittance for multiple frequencies.

source
OpticalSpectroscopy.refractive_indexFunction
refractive_index(eps1, eps2)

Compute refractive index n from real (eps1) and imaginary (eps2) parts of the dielectric function.

n = sqrt((sqrt(eps1^2 + eps2^2) + eps1) / 2)

source
OpticalSpectroscopy.extinction_coeffFunction
extinction_coeff(eps1, eps2)

Compute extinction coefficient k from real (eps1) and imaginary (eps2) parts of the dielectric function.

k = sqrt((sqrt(eps1^2 + eps2^2) - eps1) / 2)

source
OpticalSpectroscopy.cavity_mode_energyFunction
cavity_mode_energy(p, thetas)

Compute cavity photon energy as a function of incidence angle.

Ecav(theta) = E0 / sqrt(1 - (sin(theta) / neff)^2)

Arguments

  • p: Parameters [E0, neff] where E0 is normal-incidence cavity energy and neff is effective refractive index
  • thetas: Incidence angles (radians)
source
OpticalSpectroscopy.polariton_branchesFunction
polariton_branches(E_cav, E_vib, Omega)

Compute upper and lower polariton energies from the 2-level coupled oscillator model.

Epm = (Ecav + Evib) / 2 +/- sqrt(Omega^2 + (Ecav - E_vib)^2) / 2

Arguments

  • E_cav: Cavity photon energy (scalar or vector)
  • E_vib: Vibrational mode energy (scalar)
  • Omega: Rabi splitting (scalar)

Returns

(E_LP, E_UP) — lower and upper polariton energies, same shape as E_cav.

source
OpticalSpectroscopy.polariton_eigenvaluesFunction
polariton_eigenvalues(E_cav, E_vibs, Omegas)

Compute polariton energies for N vibrational modes coupled to one cavity mode.

Builds the (N+1) x (N+1) Hamiltonian and returns sorted eigenvalues. For a single vibrational mode, this reduces to polariton_branches.

The Hamiltonian eltype is promoted from the inputs, so dual numbers propagate through (ForwardDiff provides eigvals for Symmetric dual matrices), which makes the multi-mode dispersion fit differentiable.

Arguments

  • E_cav: Cavity photon energy (scalar)
  • E_vibs: Vector of vibrational mode energies
  • Omegas: Vector of Rabi splittings (one per mode)

Returns

Sorted vector of N+1 eigenvalues (polariton energies).

source
OpticalSpectroscopy.hopfield_coefficientsFunction
hopfield_coefficients(E_cav, E_vib, Omega)

Compute Hopfield coefficients (light-matter mixing fractions) for the 2-level coupled oscillator model.

At a given detuning (Ecav - Evib), returns the photonic and matter fractions for the lower and upper polariton branches.

Arguments

  • E_cav: Cavity photon energy (scalar or vector)
  • E_vib: Vibrational mode energy (scalar)
  • Omega: Rabi splitting (scalar)

Returns

Named tuple (photon_LP, matter_LP, photon_UP, matter_UP). Each element has the same shape as E_cav. Values satisfy: photon + matter = 1 for each branch.

Convention

With theta = 0.5 * atan(Omega, delta) and delta = E_cav - E_vib, the photon fraction of the LP is sin^2(theta) = (1 - delta/sqrt(delta^2 + Omega^2))/2: at far positive detuning (Ecav >> Evib) the LP converges to the bare vibration, so its photon fraction must vanish (theta -> 0, sin^2 -> 0). Verified against direct Hamiltonian diagonalization in the test suite.

source

Fitting

OpticalSpectroscopy.fit_cavity_spectrumFunction
fit_cavity_spectrum(nu, T_data; oscillators, L, n_bg,
                    R_init=0.92, phi_init=0.3, A_init=3000.0,
                    scale_init=1.0, offset_init=0.0,
                    region=nothing, fit_nu0=false, fit_Gamma=false)

Fit a cavity transmission spectrum with a multi-oscillator Fabry-Perot model.

Arguments

  • nu: Wavenumber array (cm^-1)
  • T_data: Transmittance data (fractional, 0-1)
  • oscillators: Vector of named tuples (nu0=..., Gamma=...) defining oscillator center frequencies and linewidths. These are fixed by default.
  • L: Cavity length (cm)
  • n_bg: Background refractive index
  • R_init: Initial guess for mirror reflectivity (default: 0.92)
  • phi_init: Initial guess for phase shift (default: 0.3)
  • A_init: Initial guess for oscillator amplitude (default: 3000.0)
  • scale_init: Initial guess for scale factor (default: 1.0)
  • offset_init: Initial guess for baseline offset (default: 0.0)
  • region: Optional (lo, hi) tuple to restrict fitting range
  • fit_nu0: If true, also fit oscillator center frequencies (default: false)
  • fit_Gamma: If true, also fit oscillator linewidths (default: false)

Returns

CavityFitResult with fitted parameters and auto-extracted polariton peaks.

source
fit_cavity_spectrum(s::Spectrum; L=nothing, percent=nothing, kwargs...)

Fit a cavity transmission Spectrum. Pulls wavenumber/transmittance from the spectrum's axes, converts percent transmittance to fractional when the :yunit token is :percent (override with the percent keyword), and uses the :cavity_length metadata token as L when it is not passed explicitly. Remaining keywords forward to the vector method; with no L available the vector method's UndefKeywordError(:L) propagates.

source
OpticalSpectroscopy.fit_dispersionFunction
fit_dispersion(lp_angles, lp_positions, up_angles, up_positions;
               molecular_modes, E0_init=nothing, n_eff_init=1.5, Omega_init=20.0)

Fit the coupled oscillator model to polariton dispersion data.

LP and UP data can be measured at different angles (common in experiments where only the photon-like branch is visible at large detuning).

For a single molecular mode, fits the analytic 2-level model: ELP, EUP = (Ecav + Evib)/2 +/- sqrt(Omega^2 + (Ecav - Evib)^2)/2

where Ecav(theta) = E0 / sqrt(1 - (sin(theta)/neff)^2). For multiple molecular modes, LP/UP are the lowest/highest eigenvalues of the (N+1)-level Hamiltonian (polariton_eigenvalues).

Arguments

  • lp_angles: Incidence angles for LP data (radians)
  • lp_positions: Lower polariton energies (cm^-1)
  • up_angles: Incidence angles for UP data (radians)
  • up_positions: Upper polariton energies (cm^-1)
  • molecular_modes: Scalar or vector of molecular mode energies (cm^-1), fixed
  • E0_init: Initial guess for normal-incidence cavity energy (default: min of LP - 10)
  • n_eff_init: Initial guess for effective refractive index (default: 1.5)
  • Omega_init: Initial guess for Rabi splitting (default: 20.0)

Returns

DispersionFitResult

source
fit_dispersion(angles, lp_positions, up_positions; kwargs...)

Convenience method when LP and UP are measured at the same angles.

source
fit_dispersion(results::Vector{CavityFitResult}; molecular_modes, angles)

Extract LP/UP peak positions from a vector of CavityFitResults and fit the coupled oscillator model.

Arguments

  • results: Vector of cavity fit results (one per detuning/angle)
  • molecular_modes: Molecular mode energy or vector of energies (cm^-1)
  • angles: Vector of incidence angles (radians). Must match length of results.
source
StatsAPI.predictMethod
predict(result::CavityFitResult)
predict(result::CavityFitResult, nu)

Return fitted transmittance on the original wavenumber grid, or on a custom wavenumber array nu. Extends CurveFit.predict.

source
StatsAPI.residualsMethod
residuals(result::CavityFitResult)

Return residuals (data - fit) on the original wavenumber grid. Extends CurveFit.residuals.

source