1use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::functions::*;
8use super::functions::{BOLTZMANN_K, PLANCK_H};
9
10#[allow(dead_code)]
15pub struct RenormalizationGroup {
16 pub initial_coupling: f64,
18 pub scale_factor: f64,
20}
21impl RenormalizationGroup {
22 pub fn new(initial_coupling: f64, scale_factor: f64) -> Self {
24 Self {
25 initial_coupling,
26 scale_factor,
27 }
28 }
29 pub fn iterate<F>(&self, rg_map: F, n_steps: usize) -> Vec<f64>
33 where
34 F: Fn(f64) -> f64,
35 {
36 let mut trajectory = vec![self.initial_coupling];
37 let mut g = self.initial_coupling;
38 for _ in 0..n_steps {
39 g = rg_map(g);
40 trajectory.push(g);
41 }
42 trajectory
43 }
44 pub fn find_fixed_point<F>(&self, rg_map: &F, tol: f64, max_iter: usize) -> (f64, bool)
48 where
49 F: Fn(f64) -> f64,
50 {
51 let mut g = self.initial_coupling;
52 for _ in 0..max_iter {
53 let g_new = rg_map(g);
54 let residual = g_new - g;
55 if residual.abs() < tol {
56 return (g_new, true);
57 }
58 g = g + 0.5 * residual;
59 }
60 let converged = (rg_map(g) - g).abs() < tol;
61 (g, converged)
62 }
63 pub fn is_stable_fixed_point<F>(&self, rg_map: &F, g_star: f64) -> Option<bool>
67 where
68 F: Fn(f64) -> f64,
69 {
70 let dg = g_star.abs() * 1e-6 + 1e-10;
71 if dg == 0.0 {
72 return None;
73 }
74 let deriv = (rg_map(g_star + dg) - rg_map(g_star - dg)) / (2.0 * dg);
75 Some(deriv.abs() < 1.0)
76 }
77 pub fn scaling_exponent<F>(&self, rg_map: &F, g_star: f64) -> f64
79 where
80 F: Fn(f64) -> f64,
81 {
82 let dg = g_star.abs() * 1e-6 + 1e-10;
83 let deriv = (rg_map(g_star + dg) - rg_map(g_star - dg)) / (2.0 * dg);
84 if self.scale_factor > 1.0 && deriv.abs() > 0.0 {
85 deriv.abs().ln() / self.scale_factor.ln()
86 } else {
87 0.0
88 }
89 }
90}
91#[allow(dead_code)]
96pub struct GrandCanonicalEnsemble {
97 pub energy_levels: Vec<f64>,
99 pub temperature: f64,
101 pub chemical_potential: f64,
103 pub is_fermionic: bool,
105}
106impl GrandCanonicalEnsemble {
107 pub fn new(energy_levels: Vec<f64>, temperature: f64, mu: f64, fermionic: bool) -> Self {
109 Self {
110 energy_levels,
111 temperature,
112 chemical_potential: mu,
113 is_fermionic: fermionic,
114 }
115 }
116 pub fn beta(&self) -> f64 {
118 1.0 / (BOLTZMANN_K * self.temperature)
119 }
120 pub fn mean_occupation(&self, k: usize) -> f64 {
123 let x = self.beta() * (self.energy_levels[k] - self.chemical_potential);
124 if self.is_fermionic {
125 1.0 / (x.exp() + 1.0)
126 } else {
127 if x <= 0.0 {
128 f64::INFINITY
129 } else {
130 1.0 / (x.exp() - 1.0)
131 }
132 }
133 }
134 pub fn grand_potential(&self) -> f64 {
136 let b = self.beta();
137 let sum: f64 = self
138 .energy_levels
139 .iter()
140 .map(|&eps| {
141 let x = b * (self.chemical_potential - eps);
142 if self.is_fermionic {
143 (1.0 + x.exp()).ln()
144 } else if x < -700.0 {
145 0.0
146 } else {
147 -(1.0 - x.exp()).abs().ln()
148 }
149 })
150 .sum();
151 -BOLTZMANN_K * self.temperature * sum
152 }
153 pub fn mean_particle_number(&self) -> f64 {
155 (0..self.energy_levels.len())
156 .filter_map(|k| {
157 let n = self.mean_occupation(k);
158 if n.is_finite() {
159 Some(n)
160 } else {
161 None
162 }
163 })
164 .sum()
165 }
166 pub fn mean_energy(&self) -> f64 {
168 self.energy_levels
169 .iter()
170 .enumerate()
171 .filter_map(|(k, &eps)| {
172 let n = self.mean_occupation(k);
173 if n.is_finite() {
174 Some(eps * n)
175 } else {
176 None
177 }
178 })
179 .sum()
180 }
181}
182#[allow(dead_code)]
184pub struct CorrelationFunction {
185 pub samples: Vec<f64>,
187}
188impl CorrelationFunction {
189 pub fn new(samples: Vec<f64>) -> Self {
191 Self { samples }
192 }
193 pub fn mean(&self) -> f64 {
195 if self.samples.is_empty() {
196 return 0.0;
197 }
198 self.samples.iter().sum::<f64>() / self.samples.len() as f64
199 }
200 pub fn variance(&self) -> f64 {
202 if self.samples.is_empty() {
203 return 0.0;
204 }
205 let m = self.mean();
206 let m2 = self.samples.iter().map(|&x| x * x).sum::<f64>() / self.samples.len() as f64;
207 m2 - m * m
208 }
209 pub fn connected_correlator(&self, lag: usize) -> f64 {
211 let n = self.samples.len();
212 if lag >= n {
213 return 0.0;
214 }
215 let m = self.mean();
216 let count = (n - lag) as f64;
217 let corr: f64 = (0..n - lag)
218 .map(|i| self.samples[i] * self.samples[i + lag])
219 .sum::<f64>()
220 / count;
221 corr - m * m
222 }
223 pub fn integrated_autocorrelation_time(&self, max_lag: usize) -> f64 {
225 let c0 = self.connected_correlator(0);
226 if c0.abs() < 1e-300 {
227 return 0.5;
228 }
229 let sum: f64 = (1..max_lag.min(self.samples.len()))
230 .map(|tau| self.connected_correlator(tau) / c0)
231 .sum();
232 0.5 + sum
233 }
234 pub fn susceptibility(&self) -> f64 {
236 self.variance() * self.samples.len() as f64
237 }
238}
239pub struct Ensemble {
241 pub energies: Vec<f64>,
242 pub temperature: f64,
243 pub degeneracies: Vec<u32>,
244}
245impl Ensemble {
246 pub fn new(energies: Vec<f64>, temperature: f64) -> Self {
248 let n = energies.len();
249 Self {
250 energies,
251 temperature,
252 degeneracies: vec![1; n],
253 }
254 }
255 pub fn with_degeneracies(energies: Vec<f64>, degeneracies: Vec<u32>, temperature: f64) -> Self {
257 Self {
258 energies,
259 temperature,
260 degeneracies,
261 }
262 }
263 pub fn beta(&self) -> f64 {
265 1.0 / (BOLTZMANN_K * self.temperature)
266 }
267 pub fn partition_function(&self) -> f64 {
269 let beta = self.beta();
270 self.energies
271 .iter()
272 .zip(self.degeneracies.iter())
273 .map(|(&e, &g)| (g as f64) * (-beta * e).exp())
274 .sum()
275 }
276 pub fn boltzmann_factor(&self, energy: f64) -> f64 {
278 (-self.beta() * energy).exp()
279 }
280 pub fn probability(&self, state_idx: usize) -> f64 {
282 let z = self.partition_function();
283 if z == 0.0 {
284 return 0.0;
285 }
286 let e = self.energies[state_idx];
287 let g = self.degeneracies[state_idx] as f64;
288 g * self.boltzmann_factor(e) / z
289 }
290 pub fn mean_energy(&self) -> f64 {
292 self.energies
293 .iter()
294 .enumerate()
295 .map(|(i, &e)| e * self.probability(i))
296 .sum()
297 }
298 pub fn entropy(&self) -> f64 {
300 let s: f64 = self
301 .energies
302 .iter()
303 .enumerate()
304 .filter_map(|(i, _)| {
305 let p = self.probability(i);
306 if p > 1e-300 {
307 Some(-p * p.ln())
308 } else {
309 None
310 }
311 })
312 .sum();
313 BOLTZMANN_K * s
314 }
315 pub fn free_energy(&self) -> f64 {
317 let z = self.partition_function();
318 if z <= 0.0 {
319 return f64::INFINITY;
320 }
321 -BOLTZMANN_K * self.temperature * z.ln()
322 }
323 pub fn heat_capacity(&self) -> f64 {
325 let dt = self.temperature * 1e-4;
326 let e_high = {
327 let e_high = Ensemble::with_degeneracies(
328 self.energies.clone(),
329 self.degeneracies.clone(),
330 self.temperature + dt,
331 );
332 e_high.mean_energy()
333 };
334 let e_low = {
335 let e_low = Ensemble::with_degeneracies(
336 self.energies.clone(),
337 self.degeneracies.clone(),
338 self.temperature - dt,
339 );
340 e_low.mean_energy()
341 };
342 (e_high - e_low) / (2.0 * dt)
343 }
344 pub fn max_prob_state(&self) -> usize {
346 self.energies
347 .iter()
348 .enumerate()
349 .map(|(i, _)| (i, self.probability(i)))
350 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
351 .map(|(i, _)| i)
352 .unwrap_or(0)
353 }
354}
355pub struct MeanFieldIsing {
360 pub j_coupling: f64,
362 pub h_field: f64,
364 pub coordination: f64,
366 pub temperature: f64,
368}
369impl MeanFieldIsing {
370 pub fn new(j: f64, h: f64, z: f64, temp: f64) -> Self {
371 Self {
372 j_coupling: j,
373 h_field: h,
374 coordination: z,
375 temperature: temp,
376 }
377 }
378 pub fn critical_temperature(&self) -> f64 {
380 self.coordination * self.j_coupling / BOLTZMANN_K
381 }
382 pub fn reduced_temperature(&self) -> f64 {
384 let tc = self.critical_temperature();
385 (self.temperature - tc) / tc
386 }
387 fn residual(&self, m: f64) -> f64 {
389 let b = 1.0 / (BOLTZMANN_K * self.temperature);
390 let arg = b * (self.h_field + self.coordination * self.j_coupling * m);
391 m - arg.tanh()
392 }
393 pub fn solve(&self, initial_m: f64, max_iter: usize, tol: f64) -> (f64, bool) {
397 let mut m = initial_m;
398 for _ in 0..max_iter {
399 let b = 1.0 / (BOLTZMANN_K * self.temperature);
400 let arg = b * (self.h_field + self.coordination * self.j_coupling * m);
401 let m_new = arg.tanh();
402 if (m_new - m).abs() < tol {
403 return (m_new, true);
404 }
405 m = 0.8 * m + 0.2 * m_new;
406 }
407 let converged = self.residual(m).abs() < tol;
408 (m, converged)
409 }
410 pub fn find_all_solutions(&self) -> Vec<f64> {
414 let seeds = [-0.999, -0.5, 0.0, 0.5, 0.999];
415 let mut solutions: Vec<f64> = Vec::new();
416 for &seed in &seeds {
417 let (m, converged) = self.solve(seed, 2000, 1e-10);
418 if converged {
419 let is_new = solutions
420 .iter()
421 .all(|&existing| (existing - m).abs() > 1e-6);
422 if is_new {
423 solutions.push(m);
424 }
425 }
426 }
427 solutions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
428 solutions
429 }
430 pub fn free_energy_density(&self, m: f64) -> f64 {
432 let b = 1.0 / (BOLTZMANN_K * self.temperature);
433 let interaction = -0.5 * self.coordination * self.j_coupling * m * m;
434 let field_term = -self.h_field * m;
435 let h_eff = self.h_field + self.coordination * self.j_coupling * m;
436 let entropy_term = if (b * h_eff).abs() < 700.0 {
437 -(b * h_eff).cosh().ln() / b
438 } else {
439 -(b * h_eff).abs() / b
440 };
441 interaction + field_term + entropy_term
442 }
443}
444#[derive(Debug, Clone)]
446pub struct CriticalExponents {
447 pub name: &'static str,
449 pub dimension: u8,
451 pub alpha: f64,
453 pub beta: f64,
455 pub gamma: f64,
457 pub delta: f64,
459 pub nu: f64,
461 pub eta: f64,
463}
464impl CriticalExponents {
465 pub fn check_widom(&self) -> bool {
467 let lhs = self.gamma;
468 let rhs = self.beta * (self.delta - 1.0);
469 (lhs - rhs).abs() < 0.01
470 }
471 pub fn check_rushbrooke(&self) -> bool {
473 let sum = self.alpha + 2.0 * self.beta + self.gamma;
474 (sum - 2.0).abs() < 0.01
475 }
476 pub fn check_fisher(&self) -> bool {
478 let lhs = self.gamma;
479 let rhs = self.nu * (2.0 - self.eta);
480 (lhs - rhs).abs() < 0.05
481 }
482}
483pub struct LandauFreeEnergy {
488 pub a: f64,
490 pub b: f64,
492 pub c: f64,
494 pub h: f64,
496}
497impl LandauFreeEnergy {
498 pub fn new_second_order(a: f64, b: f64, h: f64) -> Self {
500 Self { a, b, c: 0.0, h }
501 }
502 pub fn new_tricritical(a: f64, c: f64, h: f64) -> Self {
504 Self { a, b: 0.0, c, h }
505 }
506 pub fn eval(&self, m: f64, t: f64) -> f64 {
508 self.a * t * m * m + self.b * m * m * m * m + self.c * m.powf(6.0) + self.h * m
509 }
510 pub fn deriv(&self, m: f64, t: f64) -> f64 {
512 2.0 * self.a * t * m + 4.0 * self.b * m * m * m + 6.0 * self.c * m.powf(5.0) + self.h
513 }
514 pub fn deriv2(&self, m: f64, t: f64) -> f64 {
516 2.0 * self.a * t + 12.0 * self.b * m * m + 30.0 * self.c * m.powf(4.0)
517 }
518 pub fn minimize(&self, t: f64, initial_m: f64, tol: f64, max_iter: usize) -> (f64, f64, bool) {
522 let mut m = initial_m;
523 for _ in 0..max_iter {
524 let f1 = self.deriv(m, t);
525 let f2 = self.deriv2(m, t);
526 if f2.abs() < 1e-300 {
527 break;
528 }
529 let step = -f1 / f2;
530 let step = step.clamp(-0.5, 0.5);
531 m += step;
532 if step.abs() < tol {
533 return (m, self.eval(m, t), true);
534 }
535 }
536 (m, self.eval(m, t), self.deriv(m, t).abs() < tol * 100.0)
537 }
538 pub fn equilibrium_order_parameter(&self, t: f64) -> f64 {
542 if t >= 0.0 || self.b <= 0.0 {
543 let (m, _, _) = self.minimize(t, 0.01, 1e-10, 1000);
544 return m;
545 }
546 let m_mf = (-self.a * t / (2.0 * self.b)).sqrt();
547 let (m_pos, f_pos, _) = self.minimize(t, m_mf, 1e-10, 1000);
548 let (m_neg, f_neg, _) = self.minimize(t, -m_mf, 1e-10, 1000);
549 if f_pos <= f_neg {
550 m_pos
551 } else {
552 m_neg
553 }
554 }
555 pub fn spontaneous_magnetization_curve(&self, n_points: usize) -> Vec<(f64, f64)> {
557 (0..n_points)
558 .map(|i| {
559 let t = -1.0 + (i as f64) / (n_points as f64);
560 let m = self.equilibrium_order_parameter(t);
561 (t, m.abs())
562 })
563 .collect()
564 }
565}
566pub struct CanonicalEnsemble {
570 inner: Ensemble,
571}
572impl CanonicalEnsemble {
573 pub fn new(energies: Vec<f64>, temperature: f64) -> Self {
575 Self {
576 inner: Ensemble::new(energies, temperature),
577 }
578 }
579 pub fn with_degeneracies(energies: Vec<f64>, degeneracies: Vec<u32>, temperature: f64) -> Self {
581 Self {
582 inner: Ensemble::with_degeneracies(energies, degeneracies, temperature),
583 }
584 }
585 pub fn partition_function(&self) -> f64 {
587 self.inner.partition_function()
588 }
589 pub fn free_energy(&self) -> f64 {
591 self.inner.free_energy()
592 }
593 pub fn mean_energy(&self) -> f64 {
595 self.inner.mean_energy()
596 }
597 pub fn entropy(&self) -> f64 {
599 (self.inner.mean_energy() - self.inner.free_energy()) / self.inner.temperature
600 }
601 pub fn heat_capacity(&self) -> f64 {
603 self.inner.heat_capacity()
604 }
605 pub fn energy_variance(&self) -> f64 {
607 let mean_e = self.inner.mean_energy();
608 let mean_e2: f64 = self
609 .inner
610 .energies
611 .iter()
612 .enumerate()
613 .map(|(i, &e)| e * e * self.inner.probability(i))
614 .sum();
615 mean_e2 - mean_e * mean_e
616 }
617 pub fn probabilities(&self) -> Vec<f64> {
619 (0..self.inner.energies.len())
620 .map(|i| self.inner.probability(i))
621 .collect()
622 }
623 pub fn kl_from_uniform(&self) -> f64 {
625 let n = self.inner.energies.len() as f64;
626 if n == 0.0 {
627 return 0.0;
628 }
629 (0..self.inner.energies.len())
630 .filter_map(|i| {
631 let p = self.inner.probability(i);
632 if p > 1e-300 {
633 Some(p * (p * n).ln())
634 } else {
635 None
636 }
637 })
638 .sum()
639 }
640}
641#[allow(dead_code)]
646pub struct VanDerWaalsGas {
647 pub a: f64,
649 pub b: f64,
651 pub temperature: f64,
653}
654impl VanDerWaalsGas {
655 pub fn new(a: f64, b: f64, temperature: f64) -> Self {
657 Self { a, b, temperature }
658 }
659 pub fn pressure(&self, v: f64) -> f64 {
663 if v <= self.b {
664 return f64::INFINITY;
665 }
666 BOLTZMANN_K * self.temperature / (v - self.b) - self.a / (v * v)
667 }
668 pub fn critical_temperature(&self) -> f64 {
670 8.0 * self.a / (27.0 * BOLTZMANN_K * self.b)
671 }
672 pub fn critical_pressure(&self) -> f64 {
674 self.a / (27.0 * self.b * self.b)
675 }
676 pub fn critical_volume(&self) -> f64 {
678 3.0 * self.b
679 }
680 pub fn reduced_temperature(&self) -> f64 {
682 self.temperature / self.critical_temperature()
683 }
684 pub fn is_supercritical(&self) -> bool {
686 self.temperature >= self.critical_temperature()
687 }
688 pub fn critical_compressibility() -> f64 {
690 3.0 / 8.0
691 }
692}
693#[allow(dead_code)]
697pub struct VirialGas {
698 pub b2: f64,
700 pub b3: f64,
702 pub temperature: f64,
704}
705impl VirialGas {
706 pub fn new(b2: f64, b3: f64, temperature: f64) -> Self {
708 Self {
709 b2,
710 b3,
711 temperature,
712 }
713 }
714 pub fn pressure(&self, density: f64) -> f64 {
716 BOLTZMANN_K
717 * self.temperature
718 * density
719 * (1.0 + self.b2 * density + self.b3 * density * density)
720 }
721 pub fn compressibility_factor(&self, density: f64) -> f64 {
723 1.0 + self.b2 * density + self.b3 * density * density
724 }
725 pub fn hard_sphere_b2(sigma: f64) -> f64 {
727 (2.0 / 3.0) * std::f64::consts::PI * sigma * sigma * sigma
728 }
729 pub fn is_above_boyle_temperature(&self) -> bool {
732 self.b2 >= 0.0
733 }
734}
735pub struct IsingModel {
737 pub spins: Vec<Vec<i8>>,
738 pub j_coupling: f64,
739 pub temperature: f64,
740}
741impl IsingModel {
742 pub fn new(n: usize, j: f64, temp: f64) -> Self {
744 let mut rng_state: u64 = 12345;
745 let spins = (0..n)
746 .map(|_| {
747 (0..n)
748 .map(|_| {
749 let r = Self::lcg_next(&mut rng_state);
750 if r < 0.5 {
751 1i8
752 } else {
753 -1i8
754 }
755 })
756 .collect()
757 })
758 .collect();
759 Self {
760 spins,
761 j_coupling: j,
762 temperature: temp,
763 }
764 }
765 pub fn energy(&self) -> f64 {
767 let n = self.spins.len();
768 let mut e = 0.0;
769 for i in 0..n {
770 for j in 0..n {
771 let s = self.spins[i][j] as f64;
772 let s_right = self.spins[i][(j + 1) % n] as f64;
773 let s_down = self.spins[(i + 1) % n][j] as f64;
774 e -= self.j_coupling * s * (s_right + s_down);
775 }
776 }
777 e
778 }
779 pub fn magnetization(&self) -> f64 {
781 let n = self.spins.len();
782 let total: i64 = self
783 .spins
784 .iter()
785 .flat_map(|row| row.iter())
786 .map(|&s| s as i64)
787 .sum();
788 (total.abs() as f64) / ((n * n) as f64)
789 }
790 pub fn metropolis_step(&mut self, rng: &mut u64) {
792 let n = self.spins.len();
793 let r1 = Self::lcg_next(rng);
794 let r2 = Self::lcg_next(rng);
795 let r3 = Self::lcg_next(rng);
796 let i = (r1 * n as f64) as usize % n;
797 let j = (r2 * n as f64) as usize % n;
798 let s = self.spins[i][j] as f64;
799 let neighbors: f64 = [
800 self.spins[(i + n - 1) % n][j] as f64,
801 self.spins[(i + 1) % n][j] as f64,
802 self.spins[i][(j + n - 1) % n] as f64,
803 self.spins[i][(j + 1) % n] as f64,
804 ]
805 .iter()
806 .sum();
807 let delta_e = 2.0 * self.j_coupling * s * neighbors;
808 let accept = if delta_e <= 0.0 {
809 true
810 } else {
811 let prob = (-delta_e / (BOLTZMANN_K * self.temperature)).exp();
812 r3 < prob
813 };
814 if accept {
815 self.spins[i][j] = -self.spins[i][j];
816 }
817 }
818 fn lcg_next(state: &mut u64) -> f64 {
820 *state = state
821 .wrapping_mul(6364136223846793005)
822 .wrapping_add(1442695040888963407);
823 (*state >> 33) as f64 / (u32::MAX as f64 + 1.0)
824 }
825}
826pub struct CriticalExponentTable {
828 pub entries: Vec<CriticalExponents>,
829}
830impl CriticalExponentTable {
831 pub fn standard() -> Self {
833 Self {
834 entries: vec![
835 CriticalExponents {
836 name: "2D Ising",
837 dimension: 2,
838 alpha: 0.0,
839 beta: 0.125,
840 gamma: 1.75,
841 delta: 15.0,
842 nu: 1.0,
843 eta: 0.25,
844 },
845 CriticalExponents {
846 name: "3D Ising",
847 dimension: 3,
848 alpha: 0.110,
849 beta: 0.326,
850 gamma: 1.237,
851 delta: 4.789,
852 nu: 0.630,
853 eta: 0.036,
854 },
855 CriticalExponents {
856 name: "3D XY",
857 dimension: 3,
858 alpha: -0.013,
859 beta: 0.346,
860 gamma: 1.316,
861 delta: 4.780,
862 nu: 0.671,
863 eta: 0.038,
864 },
865 CriticalExponents {
866 name: "3D Heisenberg",
867 dimension: 3,
868 alpha: -0.122,
869 beta: 0.365,
870 gamma: 1.386,
871 delta: 4.803,
872 nu: 0.707,
873 eta: 0.033,
874 },
875 CriticalExponents {
876 name: "Mean Field",
877 dimension: 4,
878 alpha: 0.0,
879 beta: 0.5,
880 gamma: 1.0,
881 delta: 3.0,
882 nu: 0.5,
883 eta: 0.0,
884 },
885 CriticalExponents {
886 name: "2D Potts q=3",
887 dimension: 2,
888 alpha: 0.333,
889 beta: 0.111,
890 gamma: 1.444,
891 delta: 14.0,
892 nu: 0.833,
893 eta: 0.148,
894 },
895 ],
896 }
897 }
898 pub fn find(&self, name: &str) -> Option<&CriticalExponents> {
900 self.entries.iter().find(|e| e.name == name)
901 }
902 pub fn validate_scaling_relations(&self) -> Vec<(&'static str, bool, bool, bool)> {
904 self.entries
905 .iter()
906 .map(|e| {
907 (
908 e.name,
909 e.check_widom(),
910 e.check_rushbrooke(),
911 e.check_fisher(),
912 )
913 })
914 .collect()
915 }
916}
917pub struct IdealGas {
919 pub n_particles: u64,
920 pub temperature: f64,
921 pub volume: f64,
922}
923impl IdealGas {
924 pub fn new(n: u64, t: f64, v: f64) -> Self {
925 Self {
926 n_particles: n,
927 temperature: t,
928 volume: v,
929 }
930 }
931 pub fn pressure(&self) -> f64 {
933 (self.n_particles as f64) * BOLTZMANN_K * self.temperature / self.volume
934 }
935 pub fn mean_kinetic_energy(&self) -> f64 {
937 1.5 * BOLTZMANN_K * self.temperature
938 }
939 pub fn rms_speed(&self, mass: f64) -> f64 {
941 (3.0 * BOLTZMANN_K * self.temperature / mass).sqrt()
942 }
943 pub fn entropy(&self) -> f64 {
948 let n = self.n_particles as f64;
949 let m = 1.6726e-27_f64;
950 let mean_e = self.mean_kinetic_energy();
951 let lambda_arg = 4.0 * std::f64::consts::PI * m * mean_e / (3.0 * n * PLANCK_H * PLANCK_H);
952 if lambda_arg <= 0.0 {
953 return 0.0;
954 }
955 BOLTZMANN_K * n * ((self.volume / n) * lambda_arg.powf(1.5) + 2.5)
956 }
957}
958pub struct IsingModel1D {
962 pub n_sites: usize,
964 pub j_coupling: f64,
966 pub h_field: f64,
968 pub temperature: f64,
970}
971impl IsingModel1D {
972 pub fn new(n: usize, j: f64, h: f64, temp: f64) -> Self {
974 Self {
975 n_sites: n,
976 j_coupling: j,
977 h_field: h,
978 temperature: temp,
979 }
980 }
981 pub fn beta(&self) -> f64 {
983 1.0 / (BOLTZMANN_K * self.temperature)
984 }
985 pub fn eigenvalues(&self) -> (f64, f64) {
991 let b = self.beta();
992 let bj = b * self.j_coupling;
993 let bh = b * self.h_field;
994 let exp_bj = bj.exp();
995 let cosh_bh = bh.cosh();
996 let sinh_bh = bh.sinh();
997 let disc = sinh_bh * sinh_bh + (-4.0 * bj).exp();
998 let sqrt_disc = disc.sqrt();
999 let lambda_plus = exp_bj * (cosh_bh + sqrt_disc);
1000 let lambda_minus = exp_bj * (cosh_bh - sqrt_disc);
1001 (lambda_plus, lambda_minus)
1002 }
1003 pub fn partition_function(&self) -> f64 {
1005 let (lp, lm) = self.eigenvalues();
1006 let n = self.n_sites as f64;
1007 lp.powf(n) + lm.powf(n)
1008 }
1009 pub fn free_energy_per_site(&self) -> f64 {
1011 let z = self.partition_function();
1012 if z <= 0.0 {
1013 return f64::INFINITY;
1014 }
1015 -BOLTZMANN_K * self.temperature * z.ln() / (self.n_sites as f64)
1016 }
1017 pub fn zero_field_partition_function(&self) -> f64 {
1019 let b = self.beta();
1020 let bj = b * self.j_coupling;
1021 (2.0 * bj.cosh()).powf(self.n_sites as f64)
1022 }
1023 pub fn magnetization_per_site(&self) -> f64 {
1025 let dh = self.j_coupling.abs() * 1e-6 + 1e-30;
1026 let z_plus = IsingModel1D::new(
1027 self.n_sites,
1028 self.j_coupling,
1029 self.h_field + dh,
1030 self.temperature,
1031 )
1032 .partition_function();
1033 let z_minus = IsingModel1D::new(
1034 self.n_sites,
1035 self.j_coupling,
1036 self.h_field - dh,
1037 self.temperature,
1038 )
1039 .partition_function();
1040 let z = self.partition_function();
1041 if z <= 0.0 {
1042 return 0.0;
1043 }
1044 (z_plus - z_minus) / (2.0 * dh * self.beta() * z)
1045 }
1046 pub fn susceptibility_per_site(&self) -> f64 {
1048 let dh = self.j_coupling.abs() * 1e-4 + 1e-30;
1049 let m_plus = IsingModel1D::new(
1050 self.n_sites,
1051 self.j_coupling,
1052 self.h_field + dh,
1053 self.temperature,
1054 )
1055 .magnetization_per_site();
1056 let m_minus = IsingModel1D::new(
1057 self.n_sites,
1058 self.j_coupling,
1059 self.h_field - dh,
1060 self.temperature,
1061 )
1062 .magnetization_per_site();
1063 (m_plus - m_minus) / (2.0 * dh)
1064 }
1065}