use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CriticalExponent {
pub name: String,
pub value: f64,
}
impl CriticalExponent {
pub fn new(name: &str, value: f64) -> Self {
Self {
name: name.into(),
value,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScalingResult {
pub x_scaled: Vec<f64>,
pub y_scaled: Vec<f64>,
pub exponents_used: Vec<CriticalExponent>,
}
pub fn scaling_collapse(
temperatures: &[f64],
observables: &[f64],
system_sizes: &[usize],
tc: f64,
nu: f64,
gamma_over_nu: f64,
) -> ScalingResult {
let mut x_scaled = Vec::new();
let mut y_scaled = Vec::new();
for (i, &t) in temperatures.iter().enumerate() {
if i >= observables.len() || i >= system_sizes.len() {
break;
}
let l = system_sizes[i] as f64;
let reduced_t = (t - tc) / tc;
let x = reduced_t * l.powf(1.0 / nu);
let y = observables[i] / l.powf(gamma_over_nu);
x_scaled.push(x);
y_scaled.push(y);
}
ScalingResult {
x_scaled,
y_scaled,
exponents_used: vec![
CriticalExponent::new("nu", nu),
CriticalExponent::new("gamma/nu", gamma_over_nu),
],
}
}
pub fn finite_size_order_parameter(
system_size: usize,
beta: f64,
nu: f64,
) -> f64 {
let l = system_size as f64;
l.powf(-beta / nu)
}
pub fn finite_size_susceptibility(
system_size: usize,
gamma: f64,
nu: f64,
) -> f64 {
let l = system_size as f64;
l.powf(gamma / nu)
}
pub fn finite_size_correlation_length(system_size: usize) -> f64 {
system_size as f64
}
pub fn binder_cumulant(m2: f64, m4: f64) -> f64 {
if m2.abs() < 1e-15 {
return 0.0;
}
1.0 - m4 / (3.0 * m2 * m2)
}
pub fn verify_hyperscaling(d: usize, alpha: f64, nu: f64) -> bool {
((2.0 - alpha) - d as f64 * nu).abs() < 0.05
}
pub fn find_tc_from_crossing(
temperatures: &[f64],
binder_l1: &[f64],
binder_l2: &[f64],
) -> Option<f64> {
if temperatures.len() < 2 || binder_l1.len() != temperatures.len() || binder_l2.len() != temperatures.len() {
return None;
}
for i in 0..temperatures.len() - 1 {
let diff1 = binder_l1[i] - binder_l2[i];
let diff2 = binder_l1[i + 1] - binder_l2[i + 1];
if diff1 * diff2 < 0.0 {
let t = diff1.abs() / (diff1.abs() + diff2.abs());
let tc = temperatures[i] + t * (temperatures[i + 1] - temperatures[i]);
return Some(tc);
}
}
None
}
pub fn correlation_function(spins: &[f64], max_r: usize) -> Vec<f64> {
let n = spins.len();
let mean: f64 = spins.iter().sum::<f64>() / n as f64;
let mut corr = Vec::with_capacity(max_r);
for r in 0..max_r {
if r >= n {
corr.push(0.0);
continue;
}
let mut sum = 0.0;
let mut count = 0;
for i in 0..(n - r) {
sum += spins[i] * spins[i + r];
count += 1;
}
let c = if count > 0 { sum / count as f64 - mean * mean } else { 0.0 };
corr.push(c);
}
corr
}
pub fn extract_correlation_length(corr: &[f64]) -> f64 {
if corr.len() < 2 {
return 0.0;
}
let mut r0 = 0;
while r0 < corr.len() && corr[r0].abs() < 1e-15 {
r0 += 1;
}
if r0 >= corr.len() - 1 {
return 0.0;
}
let c0 = corr[r0].abs();
let r1 = r0 + 1;
let c1 = corr[r1].abs();
if c0 < 1e-15 || c1 < 1e-15 {
return 1.0; }
let ratio = c1 / c0;
if ratio <= 0.0 {
return 1.0;
}
let xi = -1.0 / ratio.ln();
if xi.is_nan() || xi.is_infinite() || xi < 0.0 {
return f64::MAX;
}
xi
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scaling_collapse_mean_field() {
let temps = vec![0.9, 0.95, 1.05, 1.1];
let obs = vec![100.0, 400.0, 400.0, 100.0];
let sizes = vec![10, 20, 20, 10];
let result = scaling_collapse(&temps, &obs, &sizes, 1.0, 0.5, 2.0);
assert_eq!(result.x_scaled.len(), 4);
assert_eq!(result.y_scaled.len(), 4);
}
#[test]
fn test_finite_size_order_parameter_2d_ising() {
let m10 = finite_size_order_parameter(10, 1.0 / 8.0, 1.0);
let m100 = finite_size_order_parameter(100, 1.0 / 8.0, 1.0);
let ratio = m100 / m10;
let expected = 10.0_f64.powf(-1.0 / 8.0);
assert!((ratio - expected).abs() < 0.01);
}
#[test]
fn test_finite_size_susceptibility_mean_field() {
let chi10 = finite_size_susceptibility(10, 1.0, 0.5);
let chi100 = finite_size_susceptibility(100, 1.0, 0.5);
let ratio = chi100 / chi10;
assert!((ratio - 100.0).abs() < 0.1);
}
#[test]
fn test_binder_cumulant_uniform() {
let u = binder_cumulant(1.0, 1.0);
assert!((u - (1.0 - 1.0 / 3.0)).abs() < 1e-10);
}
#[test]
fn test_binder_cumulant_gaussian() {
let u = binder_cumulant(1.0, 3.0);
assert!(u.abs() < 1e-10);
}
#[test]
fn test_hyperscaling_2d_ising() {
assert!(verify_hyperscaling(2, 0.0, 1.0));
}
#[test]
fn test_hyperscaling_mean_field() {
assert!(verify_hyperscaling(4, 0.0, 0.5));
}
#[test]
fn test_correlation_function_constant() {
let spins = vec![1.0; 100];
let corr = correlation_function(&spins, 10);
assert!(corr[0].abs() < 1e-10);
}
#[test]
fn test_correlation_function_alternating() {
let spins: Vec<f64> = (0..100).map(|i| if i % 2 == 0 { 1.0 } else { -1.0 }).collect();
let corr = correlation_function(&spins, 5);
assert!(corr[1] < 0.0);
assert!(corr[2] > 0.0);
}
#[test]
fn test_find_tc_from_crossing() {
let temps = vec![0.8, 0.9, 1.0, 1.1, 1.2];
let binder_l1 = vec![0.3, 0.45, 0.55, 0.65, 0.75]; let binder_l2 = vec![0.7, 0.55, 0.45, 0.35, 0.25]; let tc = find_tc_from_crossing(&temps, &binder_l1, &binder_l2);
assert!(tc.is_some());
let tc = tc.unwrap();
assert!((tc - 0.95).abs() < 0.1);
}
#[test]
fn test_extract_correlation_length() {
let corr: Vec<f64> = (0..20).map(|r| (-r as f64 / 2.0).exp()).collect();
let xi = extract_correlation_length(&corr);
assert!((xi - 2.0).abs() < 0.5);
}
#[test]
fn test_finite_size_correlation_length() {
assert_eq!(finite_size_correlation_length(100), 100.0);
}
}