1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CriticalExponent {
11 pub name: String,
13 pub value: f64,
15}
16
17impl CriticalExponent {
18 pub fn new(name: &str, value: f64) -> Self {
19 Self {
20 name: name.into(),
21 value,
22 }
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ScalingResult {
29 pub x_scaled: Vec<f64>,
31 pub y_scaled: Vec<f64>,
33 pub exponents_used: Vec<CriticalExponent>,
35}
36
37pub fn scaling_collapse(
45 temperatures: &[f64],
46 observables: &[f64],
47 system_sizes: &[usize],
48 tc: f64,
49 nu: f64,
50 gamma_over_nu: f64,
51) -> ScalingResult {
52 let mut x_scaled = Vec::new();
53 let mut y_scaled = Vec::new();
54
55 for (i, &t) in temperatures.iter().enumerate() {
56 if i >= observables.len() || i >= system_sizes.len() {
57 break;
58 }
59 let l = system_sizes[i] as f64;
60 let reduced_t = (t - tc) / tc;
61
62 let x = reduced_t * l.powf(1.0 / nu);
64 let y = observables[i] / l.powf(gamma_over_nu);
66
67 x_scaled.push(x);
68 y_scaled.push(y);
69 }
70
71 ScalingResult {
72 x_scaled,
73 y_scaled,
74 exponents_used: vec![
75 CriticalExponent::new("nu", nu),
76 CriticalExponent::new("gamma/nu", gamma_over_nu),
77 ],
78 }
79}
80
81pub fn finite_size_order_parameter(
84 system_size: usize,
85 beta: f64,
86 nu: f64,
87) -> f64 {
88 let l = system_size as f64;
89 l.powf(-beta / nu)
90}
91
92pub fn finite_size_susceptibility(
95 system_size: usize,
96 gamma: f64,
97 nu: f64,
98) -> f64 {
99 let l = system_size as f64;
100 l.powf(gamma / nu)
101}
102
103pub fn finite_size_correlation_length(system_size: usize) -> f64 {
106 system_size as f64
107}
108
109pub fn binder_cumulant(m2: f64, m4: f64) -> f64 {
113 if m2.abs() < 1e-15 {
114 return 0.0;
115 }
116 1.0 - m4 / (3.0 * m2 * m2)
117}
118
119pub fn verify_hyperscaling(d: usize, alpha: f64, nu: f64) -> bool {
121 ((2.0 - alpha) - d as f64 * nu).abs() < 0.05
122}
123
124pub fn find_tc_from_crossing(
127 temperatures: &[f64],
128 binder_l1: &[f64],
129 binder_l2: &[f64],
130) -> Option<f64> {
131 if temperatures.len() < 2 || binder_l1.len() != temperatures.len() || binder_l2.len() != temperatures.len() {
132 return None;
133 }
134
135 for i in 0..temperatures.len() - 1 {
137 let diff1 = binder_l1[i] - binder_l2[i];
138 let diff2 = binder_l1[i + 1] - binder_l2[i + 1];
139
140 if diff1 * diff2 < 0.0 {
141 let t = diff1.abs() / (diff1.abs() + diff2.abs());
143 let tc = temperatures[i] + t * (temperatures[i + 1] - temperatures[i]);
144 return Some(tc);
145 }
146 }
147 None
148}
149
150pub fn correlation_function(spins: &[f64], max_r: usize) -> Vec<f64> {
153 let n = spins.len();
154 let mean: f64 = spins.iter().sum::<f64>() / n as f64;
155 let mut corr = Vec::with_capacity(max_r);
156
157 for r in 0..max_r {
158 if r >= n {
159 corr.push(0.0);
160 continue;
161 }
162 let mut sum = 0.0;
163 let mut count = 0;
164 for i in 0..(n - r) {
165 sum += spins[i] * spins[i + r];
166 count += 1;
167 }
168 let c = if count > 0 { sum / count as f64 - mean * mean } else { 0.0 };
169 corr.push(c);
170 }
171 corr
172}
173
174pub fn extract_correlation_length(corr: &[f64]) -> f64 {
177 if corr.len() < 2 {
178 return 0.0;
179 }
180 let mut r0 = 0;
182 while r0 < corr.len() && corr[r0].abs() < 1e-15 {
183 r0 += 1;
184 }
185 if r0 >= corr.len() - 1 {
186 return 0.0;
187 }
188 let c0 = corr[r0].abs();
189 let r1 = r0 + 1;
190 let c1 = corr[r1].abs();
191
192 if c0 < 1e-15 || c1 < 1e-15 {
193 return 1.0; }
195 let ratio = c1 / c0;
197 if ratio <= 0.0 {
198 return 1.0;
199 }
200 let xi = -1.0 / ratio.ln();
201 if xi.is_nan() || xi.is_infinite() || xi < 0.0 {
202 return f64::MAX;
203 }
204 xi
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_scaling_collapse_mean_field() {
213 let temps = vec![0.9, 0.95, 1.05, 1.1];
215 let obs = vec![100.0, 400.0, 400.0, 100.0];
216 let sizes = vec![10, 20, 20, 10];
217 let result = scaling_collapse(&temps, &obs, &sizes, 1.0, 0.5, 2.0);
218 assert_eq!(result.x_scaled.len(), 4);
219 assert_eq!(result.y_scaled.len(), 4);
220 }
221
222 #[test]
223 fn test_finite_size_order_parameter_2d_ising() {
224 let m10 = finite_size_order_parameter(10, 1.0 / 8.0, 1.0);
227 let m100 = finite_size_order_parameter(100, 1.0 / 8.0, 1.0);
228 let ratio = m100 / m10;
230 let expected = 10.0_f64.powf(-1.0 / 8.0);
231 assert!((ratio - expected).abs() < 0.01);
232 }
233
234 #[test]
235 fn test_finite_size_susceptibility_mean_field() {
236 let chi10 = finite_size_susceptibility(10, 1.0, 0.5);
238 let chi100 = finite_size_susceptibility(100, 1.0, 0.5);
239 let ratio = chi100 / chi10;
241 assert!((ratio - 100.0).abs() < 0.1);
242 }
243
244 #[test]
245 fn test_binder_cumulant_uniform() {
246 let u = binder_cumulant(1.0, 1.0);
248 assert!((u - (1.0 - 1.0 / 3.0)).abs() < 1e-10);
249 }
250
251 #[test]
252 fn test_binder_cumulant_gaussian() {
253 let u = binder_cumulant(1.0, 3.0);
256 assert!(u.abs() < 1e-10);
257 }
258
259 #[test]
260 fn test_hyperscaling_2d_ising() {
261 assert!(verify_hyperscaling(2, 0.0, 1.0));
263 }
264
265 #[test]
266 fn test_hyperscaling_mean_field() {
267 assert!(verify_hyperscaling(4, 0.0, 0.5));
269 }
270
271 #[test]
272 fn test_correlation_function_constant() {
273 let spins = vec![1.0; 100];
274 let corr = correlation_function(&spins, 10);
275 assert!(corr[0].abs() < 1e-10);
277 }
278
279 #[test]
280 fn test_correlation_function_alternating() {
281 let spins: Vec<f64> = (0..100).map(|i| if i % 2 == 0 { 1.0 } else { -1.0 }).collect();
282 let corr = correlation_function(&spins, 5);
283 assert!(corr[1] < 0.0);
285 assert!(corr[2] > 0.0);
287 }
288
289 #[test]
290 fn test_find_tc_from_crossing() {
291 let temps = vec![0.8, 0.9, 1.0, 1.1, 1.2];
293 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);
297 assert!(tc.is_some());
298 let tc = tc.unwrap();
299 assert!((tc - 0.95).abs() < 0.1);
300 }
301
302 #[test]
303 fn test_extract_correlation_length() {
304 let corr: Vec<f64> = (0..20).map(|r| (-r as f64 / 2.0).exp()).collect();
306 let xi = extract_correlation_length(&corr);
307 assert!((xi - 2.0).abs() < 0.5);
308 }
309
310 #[test]
311 fn test_finite_size_correlation_length() {
312 assert_eq!(finite_size_correlation_length(100), 100.0);
313 }
314}