use crate::error::{self, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HpOrder {
Linear,
Quadratic,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HolsteinPrimakoff {
pub spin: f64,
pub expansion_order: HpOrder,
}
impl HolsteinPrimakoff {
pub fn new(spin: f64) -> Result<Self> {
if spin <= 0.0 {
return Err(error::invalid_param(
"spin",
"spin quantum number must be positive",
));
}
let twice_spin = 2.0 * spin;
let rounded = twice_spin.round();
if (twice_spin - rounded).abs() > 1e-9 {
return Err(error::invalid_param(
"spin",
"spin must be a positive multiple of 1/2 (half-integer or integer)",
));
}
Ok(Self {
spin,
expansion_order: HpOrder::Linear,
})
}
pub fn with_order(self, order: HpOrder) -> Self {
Self {
expansion_order: order,
..self
}
}
pub fn ferromagnet_default() -> Self {
Self {
spin: 1.0,
expansion_order: HpOrder::Linear,
}
}
pub fn antiferromagnet_default() -> Self {
Self {
spin: 0.5,
expansion_order: HpOrder::Quadratic,
}
}
#[inline]
pub fn transform_sz(&self, n_boson: f64) -> f64 {
self.spin - n_boson
}
#[inline]
pub fn transform_splus_coeff(&self) -> f64 {
(2.0 * self.spin).sqrt()
}
#[inline]
pub fn transform_sminus_coeff(&self) -> f64 {
(2.0 * self.spin).sqrt()
}
pub fn quadratic_correction(&self, n_boson: f64) -> f64 {
-n_boson * (n_boson - 1.0) / (4.0 * self.spin)
}
pub fn commutator_check(&self, n1: f64, n2: f64, tol: f64) -> bool {
let commutator_val = 2.0 * (self.spin - n1);
let two_sz = 2.0 * self.transform_sz(n2);
let deviation = (commutator_val - two_sz).abs() / (2.0 * self.spin);
deviation < tol
}
pub fn maximum_n_boson(&self) -> f64 {
2.0 * self.spin
}
pub fn validity_warning_threshold(&self) -> f64 {
0.1 * self.spin
}
pub fn validity_fraction(&self, n_boson: f64) -> f64 {
n_boson / (2.0 * self.spin)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_negative_spin_rejected() {
assert!(HolsteinPrimakoff::new(-0.5).is_err());
}
#[test]
fn test_zero_spin_rejected() {
assert!(HolsteinPrimakoff::new(0.0).is_err());
}
#[test]
fn test_non_half_integer_rejected() {
assert!(HolsteinPrimakoff::new(0.3).is_err());
assert!(HolsteinPrimakoff::new(1.7).is_err());
}
#[test]
fn test_valid_half_integer_spins() {
assert!(HolsteinPrimakoff::new(0.5).is_ok());
assert!(HolsteinPrimakoff::new(1.0).is_ok());
assert!(HolsteinPrimakoff::new(1.5).is_ok());
assert!(HolsteinPrimakoff::new(2.0).is_ok());
assert!(HolsteinPrimakoff::new(5.0).is_ok());
}
#[test]
fn test_transform_sz_at_n_zero_returns_spin() {
let hp = HolsteinPrimakoff::new(1.5).expect("valid");
let sz = hp.transform_sz(0.0);
assert!((sz - 1.5).abs() < 1e-12, "Sz at n=0 should equal S={}", 1.5);
}
#[test]
fn test_transform_sz_at_n_equals_spin_returns_zero() {
let hp = HolsteinPrimakoff::new(2.0).expect("valid");
let sz = hp.transform_sz(2.0);
assert!(sz.abs() < 1e-12, "Sz at n=S should be 0, got {}", sz);
}
#[test]
fn test_splus_sminus_coefficients_equal_sqrt_2s() {
for &spin in &[0.5_f64, 1.0, 1.5, 2.0, 5.0] {
let hp = HolsteinPrimakoff::new(spin).expect("valid");
let expected = (2.0 * spin).sqrt();
let cp = hp.transform_splus_coeff();
let cm = hp.transform_sminus_coeff();
assert!(
(cp - expected).abs() < 1e-12,
"S^+ coeff mismatch for S={}: got {cp}, expected {expected}",
spin
);
assert!(
(cm - expected).abs() < 1e-12,
"S^- coeff mismatch for S={}: got {cm}, expected {expected}",
spin
);
}
}
#[test]
fn test_quadratic_correction_negative_for_n_greater_than_one() {
let hp = HolsteinPrimakoff::new(2.0).expect("valid");
let corr = hp.quadratic_correction(2.0);
assert!(
corr < 0.0,
"quadratic correction at n=2 should be negative, got {}",
corr
);
assert!((corr - (-0.25)).abs() < 1e-12);
}
#[test]
fn test_quadratic_correction_zero_at_n_zero() {
let hp = HolsteinPrimakoff::new(1.0).expect("valid");
assert!(hp.quadratic_correction(0.0).abs() < 1e-12);
}
#[test]
fn test_quadratic_correction_zero_at_n_one() {
let hp = HolsteinPrimakoff::new(1.0).expect("valid");
assert!(hp.quadratic_correction(1.0).abs() < 1e-12);
}
#[test]
fn test_commutator_check_passes_at_low_density() {
let hp = HolsteinPrimakoff::new(5.0).expect("valid");
assert!(hp.commutator_check(0.0, 0.0, 1e-10));
}
#[test]
fn test_commutator_check_fails_at_high_density() {
let hp = HolsteinPrimakoff::new(1.0).expect("valid");
assert!(!hp.commutator_check(1.8, 0.0, 1e-3));
}
#[test]
fn test_ferromagnet_default_spin_one() {
let hp = HolsteinPrimakoff::ferromagnet_default();
assert!((hp.spin - 1.0).abs() < 1e-12);
assert_eq!(hp.expansion_order, HpOrder::Linear);
}
#[test]
fn test_antiferromagnet_default_spin_half() {
let hp = HolsteinPrimakoff::antiferromagnet_default();
assert!((hp.spin - 0.5).abs() < 1e-12);
assert_eq!(hp.expansion_order, HpOrder::Quadratic);
}
#[test]
fn test_maximum_n_boson_equals_2s() {
let hp = HolsteinPrimakoff::new(3.0).expect("valid");
assert!((hp.maximum_n_boson() - 6.0).abs() < 1e-12);
}
#[test]
fn test_validity_fraction() {
let hp = HolsteinPrimakoff::new(2.0).expect("valid");
let frac = hp.validity_fraction(1.0);
assert!(
(frac - 0.25).abs() < 1e-12,
"validity fraction mismatch: {frac}"
);
}
}