use crate::constants::codata2018::BOHR_RADIUS_ANGSTROMS as A0_BOHR;
use crate::integrals::multipoles::DerivedMultipoleParams;
use crate::parameters::ParameterModel;
use crate::properties::dipole::E_ANGSTROM_TO_DEBYE;
use crate::types::{AlignedMatrix, MolecularBatch};
use super::meci::{CiState, MeciResult};
pub const OSCILLATOR_STRENGTH_FACTOR: f64 = 0.0037922207906861114;
pub const EPSILON_PEAK_FACTOR: f64 = 1.3062974e8;
pub fn compute_transition_dipoles_and_oscillator_strengths(
batch: &MolecularBatch,
model: &dyn ParameterModel,
eigenvectors: &AlignedMatrix<f64>,
active_mo_indices: &[usize],
meci_result: &mut MeciResult,
) {
let m = active_mo_indices.len();
let natoms = batch.natoms;
let lab = meci_result.microstates.len();
let mut t2 = vec![vec![vec![0.0f64; m]; m]; 3];
for (c, t2_c) in t2.iter_mut().enumerate().take(3) {
for i in 0..m {
let mo_i = active_mo_indices[i];
for j in 0..m {
let mo_j = active_mo_indices[j];
let mut val = 0.0f64;
for a in 0..natoms {
let r_a = match c {
0 => batch.x[a],
1 => batch.y[a],
_ => batch.z[a],
};
let orb_start = batch.orbital_offsets[a];
let norbs = batch.basis_types[a].num_orbitals();
let mut q_a_ij = 0.0;
for o in 0..norbs {
q_a_ij += eigenvectors.get(orb_start + o, mo_i)
* eigenvectors.get(orb_start + o, mo_j);
}
val += r_a * q_a_ij;
let z = batch.atomic_numbers[a];
if z != 1 && norbs >= 4 {
if let Some(p) = model.get_element(z) {
let mp = DerivedMultipoleParams::from_element(&p);
let d1_angstrom = mp.dd * A0_BOHR;
let s_idx = orb_start;
let p_idx = orb_start + 1 + c;
let c_si = eigenvectors.get(s_idx, mo_i);
let c_pi = eigenvectors.get(p_idx, mo_i);
let c_sj = eigenvectors.get(s_idx, mo_j);
let c_pj = eigenvectors.get(p_idx, mo_j);
val += d1_angstrom * (c_si * c_pj + c_pi * c_sj);
}
}
}
t2_c[i][j] = val;
}
}
}
let mut t4 = vec![AlignedMatrix::zeroed(lab, lab); 3];
for c in 0..3 {
for i in 0..lab {
let m_i = &meci_result.microstates[i];
for j in 0..i {
let m_j = &meci_result.microstates[j];
let mut diff_a = Vec::new();
let mut diff_b = Vec::new();
for k in 0..m {
if m_i.alpha[k] != m_j.alpha[k] {
diff_a.push(k);
}
if m_i.beta[k] != m_j.beta[k] {
diff_b.push(k);
}
}
let l_a = diff_a.len();
let l_b = diff_b.len();
if (l_a == 2 && l_b == 0) || (l_b == 2 && l_a == 0) {
let (p, q) = if l_a == 2 {
(diff_a[0], diff_a[1])
} else {
(diff_b[0], diff_b[1])
};
let dip_elem = t2[c][p][q];
let ij = if l_a == 2 {
let mut sum = (m_i.beta[p] + m_i.alpha[p]) as usize;
for idx in (p + 1)..q {
sum += (m_i.alpha[idx] + m_i.beta[idx]) as usize;
}
sum
} else {
let mut sum = m_i.beta[p] as usize;
for idx in (p + 1)..q {
sum += (m_i.alpha[idx] + m_i.beta[idx]) as usize;
}
sum + m_i.alpha[q] as usize
};
let sign = if ij % 2 == 1 { -1.0 } else { 1.0 };
let val = dip_elem * sign;
t4[c].set(i, j, val);
t4[c].set(j, i, -val);
}
}
}
}
for state_idx in 0..meci_result.states.len() {
if state_idx == 0 {
meci_result.states[0].transition_dipole_debye = [0.0; 3];
meci_result.states[0].dipole_strength_debye = 0.0;
meci_result.states[0].polarization_angstrom2 = [0.0; 3];
meci_result.states[0].oscillator_strength = 0.0;
continue;
}
let mut dip_debye = [0.0f64; 3];
let mut pol_a2 = [0.0f64; 3];
for c in 0..3 {
let mut val_angstrom = 0.0f64;
for k in 0..lab {
let c_0k = meci_result.states[0].eigenvector[k];
for l in 0..lab {
let c_nl = meci_result.states[state_idx].eigenvector[l];
val_angstrom += c_0k * t4[c].get(k, l) * c_nl;
}
}
pol_a2[c] = val_angstrom * val_angstrom;
dip_debye[c] = (val_angstrom * E_ANGSTROM_TO_DEBYE).abs();
}
let dip_sq =
dip_debye[0] * dip_debye[0] + dip_debye[1] * dip_debye[1] + dip_debye[2] * dip_debye[2];
let dip_mag = dip_sq.sqrt();
let d_e = meci_result.states[state_idx].excitation_energy_ev;
let f_osc = if d_e > 1e-4 {
OSCILLATOR_STRENGTH_FACTOR * d_e * dip_sq
} else {
0.0
};
meci_result.states[state_idx].transition_dipole_debye = dip_debye;
meci_result.states[state_idx].dipole_strength_debye = dip_mag;
meci_result.states[state_idx].polarization_angstrom2 = pol_a2;
meci_result.states[state_idx].oscillator_strength = f_osc;
}
}
#[derive(Debug, Clone)]
pub struct UvVisSpectrum {
pub wavelengths_nm: Vec<f64>,
pub extinction_coefficients: Vec<f64>,
pub lambda_max_nm: f64,
pub epsilon_max: f64,
}
pub fn simulate_uv_vis_spectrum(
states: &[CiState],
min_wl_nm: f64,
max_wl_nm: f64,
step_nm: f64,
fwhm_nm: f64,
) -> UvVisSpectrum {
let n_points = (((max_wl_nm - min_wl_nm) / step_nm).floor() as usize) + 1;
let mut wavelengths_nm = Vec::with_capacity(n_points);
let mut extinction_coefficients = vec![0.0f64; n_points];
for idx in 0..n_points {
wavelengths_nm.push(min_wl_nm + (idx as f64) * step_nm);
}
let sigma = fwhm_nm / (2.0 * (2.0f64.ln() * 2.0).sqrt());
let two_sigma_sq = 2.0 * sigma * sigma;
let norm_factor = 1.0 / (sigma * (2.0 * std::f64::consts::PI).sqrt());
for state in states {
if state.excitation_energy_ev <= 1e-4 || state.wavelength_nm <= 0.0 {
continue;
}
let wl_0 = state.wavelength_nm;
let f = state.oscillator_strength;
if f <= 1e-6 {
continue;
}
let peak_eps = f * EPSILON_PEAK_FACTOR * norm_factor;
for (idx, &wl) in wavelengths_nm.iter().enumerate() {
let diff = wl - wl_0;
if diff.abs() > 4.0 * fwhm_nm {
continue;
}
let gauss = (-diff * diff / two_sigma_sq).exp();
extinction_coefficients[idx] += peak_eps * gauss;
}
}
let mut lambda_max_nm = min_wl_nm;
let mut epsilon_max = 0.0f64;
for (idx, &eps) in extinction_coefficients.iter().enumerate() {
if eps > epsilon_max {
epsilon_max = eps;
lambda_max_nm = wavelengths_nm[idx];
}
}
UvVisSpectrum {
wavelengths_nm,
extinction_coefficients,
lambda_max_nm,
epsilon_max,
}
}