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:
- Multi-oscillator Lorentz dielectric function (via CurveFitModels.jl)
- Complex refractive index $n$, $k$ from the dielectric function
- Fabry-Pérot Airy transmittance with an absorbing intracavity medium
- Coupled oscillator polariton model: branches, eigenvalues, mixing fractions
- 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 angleFitting 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 gridFitting 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 detuningConventions
- 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.CavityFitResult — Type
CavityFitResultResult from fitting a single cavity transmission spectrum.
Fields
R: Mirror reflectivityL: Cavity length (cm)n_bg: Background refractive indexphi: Phase shiftscale: Scale factor applied to transmittanceoffset: Baseline offsetoscillators: Vector of NamedTuples(nu0, Gamma, A)for each oscillatorpolariton_peaks: Vector of peak positions (cm^-1) extracted from fitrsquared: 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)
OpticalSpectroscopy.DispersionFitResult — Type
DispersionFitResultResult 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 indexE0: Normal-incidence cavity energy (cm^-1)rabi_err: Uncertainty in Rabi splittingn_eff_err: Uncertainty in n_effE0_err: Uncertainty in E0lp_angles: Incidence angles for LP data (radians)lp_positions: Lower polariton positions at each LP angleup_angles: Incidence angles for UP data (radians)up_positions: Upper polariton positions at each UP anglehopfield_zero: Hopfield coefficients at zero detuningrsquared: R^2 goodness of fit_sol: CurveFit solution object (internal)
Physics
OpticalSpectroscopy.cavity_transmittance — Function
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 coefficientL: Cavity lengthR: 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
OpticalSpectroscopy.compute_cavity_transmittance — Function
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:
- Sum Lorentz oscillator dielectric contributions
- Compute complex refractive index (n, k) from dielectric function (
refractive_index,extinction_coeff) - Compute absorption coefficient alpha = 4pi * k * nu
- 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 reflectivityL: Cavity length (cm)n_bg: Background refractive indexphi: Phase shift upon reflection
ForwardDiff-compatible.
compute_cavity_transmittance(nus::AbstractArray, nu0s, Gammas, As, R, L, n_bg, phi)Array dispatch: compute cavity transmittance for multiple frequencies.
OpticalSpectroscopy.refractive_index — Function
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)
OpticalSpectroscopy.extinction_coeff — Function
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)
OpticalSpectroscopy.cavity_mode_energy — Function
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 indexthetas: Incidence angles (radians)
OpticalSpectroscopy.polariton_branches — Function
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.
OpticalSpectroscopy.polariton_eigenvalues — Function
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 energiesOmegas: Vector of Rabi splittings (one per mode)
Returns
Sorted vector of N+1 eigenvalues (polariton energies).
OpticalSpectroscopy.hopfield_coefficients — Function
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.
Fitting
OpticalSpectroscopy.fit_cavity_spectrum — Function
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 indexR_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 rangefit_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.
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.
OpticalSpectroscopy.fit_dispersion — Function
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), fixedE0_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
fit_dispersion(angles, lp_positions, up_positions; kwargs...)Convenience method when LP and UP are measured at the same angles.
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 ofresults.
StatsAPI.predict — Method
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.
StatsAPI.residuals — Method
residuals(result::CavityFitResult)Return residuals (data - fit) on the original wavenumber grid. Extends CurveFit.residuals.
OpticalSpectroscopy.wavenumber — Method
wavenumber(r::CavityFitResult) -> Vector{Float64}Return the wavenumber array used in the fit.