use std::f64::consts::PI;
pub const C_LIGHT: f64 = 2.99792458e8;
pub const H_PLANCK: f64 = 6.62607015e-34;
pub const HBAR: f64 = 1.054571817e-34;
pub const KB: f64 = 1.380649e-23;
#[derive(Debug, Clone)]
pub struct ShgMicroscope {
pub excitation_wavelength_m: f64,
pub na: f64,
pub n_medium: f64,
pub pixel_size_m: f64,
}
impl ShgMicroscope {
pub fn new(excitation_wavelength_m: f64, na: f64, n_medium: f64, pixel_size_m: f64) -> Self {
Self {
excitation_wavelength_m,
na,
n_medium,
pixel_size_m,
}
}
pub fn shg_signal(&self, intensity_w_m2: f64, chi2_eff: f64, interaction_length_m: f64) -> f64 {
let omega = 2.0 * PI * C_LIGHT / self.excitation_wavelength_m;
let prefactor = (omega / C_LIGHT).powi(2);
let geom = interaction_length_m * interaction_length_m * 0.25;
prefactor * chi2_eff.powi(2) * intensity_w_m2.powi(2) * geom
}
pub fn phase_mismatch(&self, n_omega: f64, n_2omega: f64) -> f64 {
let lambda_omega = self.excitation_wavelength_m;
2.0 * (2.0 * PI / lambda_omega) * (n_2omega - n_omega)
}
pub fn coherence_length(&self, n_omega: f64, n_2omega: f64) -> f64 {
let dk = self.phase_mismatch(n_omega, n_2omega).abs();
if dk < 1e-30 {
f64::INFINITY
} else {
PI / dk
}
}
pub fn axial_resolution_m(&self) -> f64 {
0.89 * self.excitation_wavelength_m / (self.n_medium * self.na * self.na)
}
pub fn lateral_resolution_m(&self) -> f64 {
let lambda_shg = self.excitation_wavelength_m * 0.5;
0.51 * lambda_shg / self.na
}
pub fn psf_intensity(&self, x: f64, y: f64, z: f64) -> f64 {
let w_r = self.lateral_resolution_m() / (2.0 * (2.0_f64.ln()).sqrt());
let w_z = self.axial_resolution_m() / (2.0 * (2.0_f64.ln()).sqrt());
let r2 = x * x + y * y;
let arg_r = -2.0 * r2 / (w_r * w_r);
let arg_z = -2.0 * z * z / (w_z * w_z);
(arg_r + arg_z).exp().powi(2)
}
pub fn fb_ratio(&self, sample_thickness_m: f64, n_omega: f64, n_2omega: f64) -> f64 {
let dk = self.phase_mismatch(n_omega, n_2omega);
let phi = dk * sample_thickness_m;
let cos_phi = phi.cos();
let forward = (1.0 + cos_phi) * 0.5 + 0.5; let backward = (1.0 - cos_phi) * 0.5 + 1e-9;
forward / backward
}
pub fn shg_wavelength_m(&self) -> f64 {
self.excitation_wavelength_m * 0.5
}
}
#[derive(Debug, Clone)]
pub struct ThgMicroscope {
pub excitation_wavelength_m: f64,
pub na: f64,
pub n_medium: f64,
}
impl ThgMicroscope {
pub fn new(excitation_wavelength_m: f64, na: f64, n_medium: f64) -> Self {
Self {
excitation_wavelength_m,
na,
n_medium,
}
}
pub fn thg_signal(&self, intensity_w_m2: f64, chi3_eff: f64, dz_m: f64) -> f64 {
let omega = 2.0 * PI * C_LIGHT / self.excitation_wavelength_m;
let z_r = self.rayleigh_length_m();
let prefactor = (omega / C_LIGHT).powi(2);
let thickness_factor = (dz_m / z_r).powi(2);
prefactor * chi3_eff.powi(2) * intensity_w_m2.powi(3) * thickness_factor
}
pub fn rayleigh_length_m(&self) -> f64 {
let w0 = self.excitation_wavelength_m / (PI * self.na / self.n_medium);
PI * self.n_medium * w0 * w0 / self.excitation_wavelength_m
}
pub fn interface_enhancement_factor(&self, dn_over_n: f64) -> f64 {
dn_over_n * dn_over_n
}
pub fn axial_resolution_m(&self) -> f64 {
0.67 * self.excitation_wavelength_m / (self.n_medium * self.na * self.na)
}
pub fn lateral_resolution_m(&self) -> f64 {
let lambda_thg = self.excitation_wavelength_m / 3.0;
0.51 * lambda_thg / self.na
}
pub fn thg_wavelength_m(&self) -> f64 {
self.excitation_wavelength_m / 3.0
}
}
#[derive(Debug, Clone)]
pub struct CollagenShg {
pub chi2_eff: f64,
pub fiber_diameter_m: f64,
pub coherence_length_m: f64,
}
impl CollagenShg {
pub fn new(chi2_eff: f64, fiber_diameter_m: f64, coherence_length_m: f64) -> Self {
Self {
chi2_eff,
fiber_diameter_m,
coherence_length_m,
}
}
pub fn signal_vs_angle(&self, theta: f64, intensity: f64) -> f64 {
let rho: f64 = 1.4;
let (sin_t, cos_t) = theta.sin_cos();
let tensor_factor = cos_t * cos_t + rho * sin_t * sin_t;
let lc = self.coherence_length_m.max(1e-30);
let dk_l_half = PI * self.fiber_diameter_m / (2.0 * lc);
let sinc_sq = if dk_l_half.abs() < 1e-12 {
1.0
} else {
(dk_l_half.sin() / dk_l_half).powi(2)
};
self.chi2_eff.powi(2) * intensity.powi(2) * tensor_factor.powi(2) * sinc_sq
}
pub fn anisotropy_ratio(&self) -> f64 {
let rho: f64 = 1.4;
(1.0 + rho * rho) / (2.0 * rho)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_shg() -> ShgMicroscope {
ShgMicroscope::new(1040e-9, 1.2, 1.333, 100e-9)
}
#[test]
fn test_shg_signal_scales_quadratically_with_intensity() {
let scope = default_shg();
let chi2 = 1.0e-11; let l = 10e-6;
let i1 = scope.shg_signal(1e12, chi2, l);
let i2 = scope.shg_signal(2e12, chi2, l);
let ratio = i2 / i1;
assert!((ratio - 4.0).abs() < 1e-6, "Expected 4x, got {}", ratio);
}
#[test]
fn test_phase_mismatch_perfect_match() {
let scope = default_shg();
let dk = scope.phase_mismatch(1.5, 1.5);
assert!(dk.abs() < 1e-20, "Perfect match should give Δk ≈ 0");
}
#[test]
fn test_coherence_length_physical_range() {
let scope = default_shg();
let lc = scope.coherence_length(1.50, 1.51);
assert!(lc > 1e-6 && lc < 1e-3, "L_c out of physical range: {}", lc);
}
#[test]
fn test_axial_resolution_tighter_than_confocal() {
let scope = default_shg();
let dz_shg = scope.axial_resolution_m();
let dz_confocal =
1.4 * scope.excitation_wavelength_m / (scope.n_medium * scope.na * scope.na);
assert!(
dz_shg < dz_confocal,
"SHG should have tighter axial resolution"
);
}
#[test]
fn test_psf_maximum_at_origin() {
let scope = default_shg();
let i0 = scope.psf_intensity(0.0, 0.0, 0.0);
let i_off = scope.psf_intensity(200e-9, 0.0, 0.0);
assert!(i0 > i_off, "PSF maximum should be at origin");
assert!((i0 - 1.0).abs() < 1e-12, "PSF(0,0,0) should be 1.0");
}
#[test]
fn test_thg_interface_enhancement() {
let thg = ThgMicroscope::new(1200e-9, 1.2, 1.333);
let eta_small = thg.interface_enhancement_factor(0.01);
let eta_large = thg.interface_enhancement_factor(0.10);
assert!(
(eta_large / eta_small - 100.0).abs() < 1.0,
"Enhancement should scale as (Δn/n)²"
);
}
#[test]
fn test_thg_axial_tighter_than_shg() {
let shg = ShgMicroscope::new(1200e-9, 1.2, 1.333, 100e-9);
let thg = ThgMicroscope::new(1200e-9, 1.2, 1.333);
assert!(
thg.axial_resolution_m() < shg.axial_resolution_m(),
"THG should have tighter axial resolution than SHG"
);
}
#[test]
fn test_collagen_anisotropy() {
let collagen = CollagenShg::new(10e-12, 200e-9, 5e-6);
let r = collagen.anisotropy_ratio();
assert!(r > 1.0, "Anisotropy ratio must be ≥ 1 for ordered collagen");
assert!(r < 2.0, "Anisotropy ratio unrealistically large");
}
#[test]
fn test_collagen_signal_vs_angle_max_at_zero() {
let collagen = CollagenShg::new(10e-12, 200e-9, 5e-6);
let i0 = collagen.signal_vs_angle(0.0, 1e12);
let i90 = collagen.signal_vs_angle(PI / 2.0, 1e12);
assert!(i0 > 0.0 && i90 > 0.0, "SHG signal must be positive");
}
}