1#![allow(dead_code)]
20
21use std::f64::consts::PI;
22
23struct Lcg {
29 state: u64,
30}
31
32impl Lcg {
33 fn new(seed: u64) -> Self {
34 Self { state: seed.max(1) }
35 }
36
37 fn next_u64(&mut self) -> u64 {
38 self.state = self
39 .state
40 .wrapping_mul(6_364_136_223_846_793_005)
41 .wrapping_add(1_442_695_040_888_963_407);
42 self.state
43 }
44
45 fn next_f64(&mut self) -> f64 {
46 (self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
47 }
48}
49
50pub const EULER_MASCHERONI: f64 = 0.577_215_664_901_532_9;
56
57pub const FOUR_PI_SQ: f64 = 4.0 * PI * PI;
59
60pub const EPSILON_WILSON: f64 = 1.0;
62
63pub const DEFAULT_UV_CUTOFF: f64 = 1.0e3;
65
66pub const FP_TOL: f64 = 1.0e-10;
68
69pub const FP_MAX_ITER: usize = 1_000;
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum LoopOrder {
79 OneLoop,
81 TwoLoop,
83}
84
85#[derive(Debug, Clone)]
91pub struct BetaFunction {
92 pub b0: f64,
94 pub b1: f64,
96 pub order: LoopOrder,
98 pub name: String,
100}
101
102impl BetaFunction {
103 pub fn new(b0: f64, b1: f64, order: LoopOrder, name: impl Into<String>) -> Self {
105 Self {
106 b0,
107 b1,
108 order,
109 name: name.into(),
110 }
111 }
112
113 pub fn evaluate(&self, g: f64) -> f64 {
118 let one_loop = -self.b0 * g * g;
119 match self.order {
120 LoopOrder::OneLoop => one_loop,
121 LoopOrder::TwoLoop => one_loop - self.b1 * g * g * g,
122 }
123 }
124
125 pub fn derivative(&self, g: f64) -> f64 {
127 let one_loop = -2.0 * self.b0 * g;
128 match self.order {
129 LoopOrder::OneLoop => one_loop,
130 LoopOrder::TwoLoop => one_loop - 3.0 * self.b1 * g * g,
131 }
132 }
133
134 pub fn fixed_point(&self) -> Option<f64> {
138 match self.order {
139 LoopOrder::OneLoop => {
140 None
142 }
143 LoopOrder::TwoLoop => {
144 if self.b1.abs() < f64::EPSILON {
147 None
148 } else {
149 let gstar = -self.b0 / self.b1;
150 if gstar > 0.0 { Some(gstar) } else { None }
151 }
152 }
153 }
154 }
155
156 pub fn stability_exponent(&self) -> Option<f64> {
160 self.fixed_point().map(|gstar| self.derivative(gstar))
161 }
162
163 pub fn run_coupling(&self, g0: f64, t_final: f64, steps: usize) -> f64 {
166 let dt = t_final / steps as f64;
167 let mut g = g0;
168 for _ in 0..steps {
169 g += self.evaluate(g) * dt;
170 }
171 g
172 }
173
174 pub fn running_coupling_one_loop(&self, g0: f64, t: f64) -> f64 {
178 let denom = 1.0 + self.b0 * g0 * t;
179 if denom <= 0.0 {
180 f64::INFINITY
181 } else {
182 g0 / denom
183 }
184 }
185
186 pub fn landau_pole(&self, g0: f64) -> f64 {
188 if self.b0 <= 0.0 {
189 f64::INFINITY
190 } else {
191 1.0 / (2.0 * self.b0 * g0)
192 }
193 }
194}
195
196#[derive(Debug, Clone)]
202pub struct RgFlowResult {
203 pub t_values: Vec<f64>,
205 pub coupling: Vec<f64>,
207 pub mass: Vec<f64>,
209 pub eta: Vec<f64>,
211}
212
213#[derive(Debug, Clone)]
218pub struct RgFlow {
219 pub beta: BetaFunction,
221 pub gamma0: f64,
223 pub eta0: f64,
225}
226
227impl RgFlow {
228 pub fn new(beta: BetaFunction, gamma0: f64, eta0: f64) -> Self {
230 Self { beta, gamma0, eta0 }
231 }
232
233 pub fn gamma_mass(&self, g: f64) -> f64 {
235 self.gamma0 * g * g
236 }
237
238 pub fn eta(&self, g: f64) -> f64 {
240 self.eta0 * g * g
241 }
242
243 pub fn integrate(&self, g0: f64, m0: f64, t_max: f64, n: usize) -> RgFlowResult {
247 let dt = t_max / n as f64;
248 let mut t_values = Vec::with_capacity(n + 1);
249 let mut coupling = Vec::with_capacity(n + 1);
250 let mut mass = Vec::with_capacity(n + 1);
251 let mut eta = Vec::with_capacity(n + 1);
252
253 let mut g = g0;
254 let mut m = m0;
255 let mut t = 0.0;
256
257 t_values.push(t);
258 coupling.push(g);
259 mass.push(m);
260 eta.push(self.eta(g));
261
262 for _ in 0..n {
263 let dg = self.beta.evaluate(g) * dt;
264 let dm = -self.gamma_mass(g) * m * dt;
265 g += dg;
266 m += dm;
267 t += dt;
268 t_values.push(t);
269 coupling.push(g);
270 mass.push(m);
271 eta.push(self.eta(g));
272 }
273
274 RgFlowResult {
275 t_values,
276 coupling,
277 mass,
278 eta,
279 }
280 }
281
282 pub fn nu_exponent(&self) -> Option<f64> {
285 self.beta.stability_exponent().and_then(|omega| {
286 if omega.abs() < f64::EPSILON {
287 None
288 } else {
289 Some(1.0 / omega.abs())
290 }
291 })
292 }
293}
294
295#[derive(Debug, Clone)]
304pub struct WilsonianRg {
305 pub d: f64,
307 pub epsilon: f64,
309 pub lambda: f64,
311 pub step: usize,
313 pub u: f64,
315 pub r: f64,
317}
318
319impl WilsonianRg {
320 pub fn new(d: f64, lambda: f64, u: f64, r: f64) -> Self {
322 Self {
323 d,
324 epsilon: 4.0 - d,
325 lambda,
326 step: 0,
327 u,
328 r,
329 }
330 }
331
332 #[allow(clippy::too_many_arguments)]
338 pub fn step_flow(&mut self, dl: f64, n_components: f64) {
339 let du = self.epsilon * self.u - (n_components + 8.0) / (16.0 * PI * PI) * self.u * self.u;
340 let dr = 2.0 * self.r + (n_components + 2.0) / (16.0 * PI * PI) * self.u;
341 self.u += du * dl;
342 self.r += dr * dl;
343 self.step += 1;
344 }
345
346 pub fn wilson_fisher_fixed_point(&self, n_components: f64) -> f64 {
350 16.0 * PI * PI * self.epsilon / (n_components + 8.0)
351 }
352
353 pub fn eta_wf(&self, n_components: f64) -> f64 {
357 let eps = self.epsilon;
358 (n_components + 2.0) * eps * eps / (2.0 * (n_components + 8.0).powi(2))
359 }
360
361 pub fn nu_wf(&self, n_components: f64) -> f64 {
365 let inv_nu = 2.0 - (n_components + 2.0) / (n_components + 8.0) * self.epsilon;
366 1.0 / inv_nu
367 }
368
369 pub fn rescale_correlation_length(&self, xi: f64, b: f64) -> f64 {
373 xi / b
374 }
375
376 pub fn run(&mut self, n_steps: usize, dl: f64, n_components: f64) {
378 for _ in 0..n_steps {
379 self.step_flow(dl, n_components);
380 }
381 }
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub enum UniversalityClass {
391 Ising2D,
393 Ising3D,
395 Xy3D,
397 Heisenberg3D,
399 MeanField,
401}
402
403#[derive(Debug, Clone)]
405pub struct CriticalExponents {
406 pub nu: f64,
408 pub eta: f64,
410 pub beta_exp: f64,
412 pub gamma: f64,
414 pub alpha: f64,
416 pub delta: f64,
418 pub d: f64,
420}
421
422impl UniversalityClass {
423 pub fn exponents(self) -> CriticalExponents {
427 match self {
428 UniversalityClass::Ising2D => CriticalExponents {
429 nu: 1.0,
430 eta: 0.25,
431 beta_exp: 0.125,
432 gamma: 1.75,
433 alpha: 0.0, delta: 15.0,
435 d: 2.0,
436 },
437 UniversalityClass::Ising3D => CriticalExponents {
438 nu: 0.6301,
439 eta: 0.0364,
440 beta_exp: 0.3265,
441 gamma: 1.2372,
442 alpha: 0.1100,
443 delta: 4.789,
444 d: 3.0,
445 },
446 UniversalityClass::Xy3D => CriticalExponents {
447 nu: 0.6717,
448 eta: 0.0381,
449 beta_exp: 0.3486,
450 gamma: 1.3178,
451 alpha: -0.0151,
452 delta: 4.780,
453 d: 3.0,
454 },
455 UniversalityClass::Heisenberg3D => CriticalExponents {
456 nu: 0.7112,
457 eta: 0.0375,
458 beta_exp: 0.3689,
459 gamma: 1.3960,
460 alpha: -0.1336,
461 delta: 4.783,
462 d: 3.0,
463 },
464 UniversalityClass::MeanField => CriticalExponents {
465 nu: 0.5,
466 eta: 0.0,
467 beta_exp: 0.5,
468 gamma: 1.0,
469 alpha: 0.0,
470 delta: 3.0,
471 d: 4.0, },
473 }
474 }
475
476 pub fn n_components(self) -> usize {
478 match self {
479 UniversalityClass::Ising2D | UniversalityClass::Ising3D => 1,
480 UniversalityClass::Xy3D => 2,
481 UniversalityClass::Heisenberg3D => 3,
482 UniversalityClass::MeanField => 1,
483 }
484 }
485
486 pub fn dimension(self) -> f64 {
488 match self {
489 UniversalityClass::Ising2D => 2.0,
490 _ => 3.0,
491 }
492 }
493}
494
495#[derive(Debug, Clone)]
507pub struct ScalingRelations {
508 pub exp: CriticalExponents,
510}
511
512impl ScalingRelations {
513 pub fn new(exp: CriticalExponents) -> Self {
515 Self { exp }
516 }
517
518 pub fn rushbrooke(&self) -> (f64, f64) {
520 let lhs = self.exp.alpha + 2.0 * self.exp.beta_exp + self.exp.gamma;
521 (lhs, 2.0)
522 }
523
524 pub fn widom(&self) -> (f64, f64) {
526 let lhs = self.exp.gamma;
527 let rhs = self.exp.beta_exp * (self.exp.delta - 1.0);
528 (lhs, rhs)
529 }
530
531 pub fn fisher(&self) -> (f64, f64) {
533 let lhs = self.exp.gamma;
534 let rhs = (2.0 - self.exp.eta) * self.exp.nu;
535 (lhs, rhs)
536 }
537
538 pub fn josephson(&self) -> (f64, f64) {
540 let lhs = 2.0 - self.exp.alpha;
541 let rhs = self.exp.d * self.exp.nu;
542 (lhs, rhs)
543 }
544
545 pub fn check_all(&self, tol: f64) -> (bool, bool, bool, bool) {
549 let (r_lhs, r_rhs) = self.rushbrooke();
550 let (w_lhs, w_rhs) = self.widom();
551 let (f_lhs, f_rhs) = self.fisher();
552 let (j_lhs, j_rhs) = self.josephson();
553 (
554 (r_lhs - r_rhs).abs() < tol,
555 (w_lhs - w_rhs).abs() < tol,
556 (f_lhs - f_rhs).abs() < tol,
557 (j_lhs - j_rhs).abs() < tol,
558 )
559 }
560}
561
562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
568pub enum RenormalizationScheme {
569 MSbar,
571 MomentumSubtraction,
573 OnShell,
575}
576
577#[derive(Debug, Clone)]
579pub struct RenormalizedCoupling {
580 pub scheme: RenormalizationScheme,
582 pub g_bare: f64,
584 pub g_renorm: f64,
586 pub mu: f64,
588 pub divergence_degree: i32,
590}
591
592impl RenormalizedCoupling {
593 pub fn new(
595 scheme: RenormalizationScheme,
596 g_bare: f64,
597 mu: f64,
598 divergence_degree: i32,
599 ) -> Self {
600 let g_renorm = match scheme {
601 RenormalizationScheme::MSbar => {
602 let log_factor = (4.0 * PI).ln() - EULER_MASCHERONI;
604 g_bare - g_bare * g_bare * log_factor / (16.0 * PI * PI)
605 }
606 RenormalizationScheme::MomentumSubtraction => {
607 g_bare - g_bare * g_bare * mu.ln() / (16.0 * PI * PI)
609 }
610 RenormalizationScheme::OnShell => {
611 g_bare * (1.0 - g_bare / (16.0 * PI * PI))
613 }
614 };
615 Self {
616 scheme,
617 g_bare,
618 g_renorm,
619 mu,
620 divergence_degree,
621 }
622 }
623
624 pub fn convert_msbar_to_mom(&self, delta: f64) -> f64 {
628 self.g_renorm * (1.0 + delta * self.g_renorm / (16.0 * PI * PI))
629 }
630
631 pub fn run_msbar(&self, mu2: f64, b0: f64) -> f64 {
633 let t = (mu2 / self.mu).ln();
634 self.g_renorm / (1.0 + 2.0 * b0 * self.g_renorm * t).sqrt()
635 }
636}
637
638#[derive(Debug, Clone)]
644pub struct OpeCoefficient {
645 pub label_i: String,
647 pub label_j: String,
649 pub label_k: String,
651 pub value: f64,
653}
654
655impl OpeCoefficient {
656 pub fn new(
658 label_i: impl Into<String>,
659 label_j: impl Into<String>,
660 label_k: impl Into<String>,
661 value: f64,
662 ) -> Self {
663 Self {
664 label_i: label_i.into(),
665 label_j: label_j.into(),
666 label_k: label_k.into(),
667 value,
668 }
669 }
670}
671
672#[derive(Debug, Clone, Default)]
677pub struct OperatorProductExpansion {
678 pub coefficients: Vec<OpeCoefficient>,
680 pub dimensions: Vec<(String, f64)>,
682}
683
684impl OperatorProductExpansion {
685 pub fn new() -> Self {
687 Self::default()
688 }
689
690 pub fn add_coefficient(&mut self, coeff: OpeCoefficient) {
692 self.coefficients.push(coeff);
693 }
694
695 pub fn add_operator(&mut self, label: impl Into<String>, delta: f64) {
697 self.dimensions.push((label.into(), delta));
698 }
699
700 pub fn conformal_block_2d(&self, delta: f64, z: f64) -> f64 {
704 if z <= 0.0 {
705 return 0.0;
706 }
707 let z_pow = z.powf(delta / 2.0);
708 let correction = 1.0 + delta * z / (2.0 * (2.0 * delta + 1.0));
709 z_pow * correction
710 }
711
712 pub fn partial_wave_amplitude(&self, sigma_label: &str, z: f64) -> f64 {
714 let mut amplitude = 0.0;
715 for coeff in &self.coefficients {
716 if coeff.label_i == sigma_label
717 && coeff.label_j == sigma_label
718 && let Some((_, delta_k)) = self
719 .dimensions
720 .iter()
721 .find(|(lbl, _)| lbl == &coeff.label_k)
722 {
723 let g = self.conformal_block_2d(*delta_k, z);
724 amplitude += coeff.value * coeff.value * g;
725 }
726 }
727 amplitude
728 }
729
730 pub fn fusion_rule_exists(&self, label_i: &str, label_j: &str, label_k: &str) -> bool {
732 self.coefficients
733 .iter()
734 .any(|c| c.label_i == label_i && c.label_j == label_j && c.label_k == label_k)
735 }
736}
737
738#[derive(Debug, Clone)]
747pub struct CriticalPhenomena {
748 pub exp: CriticalExponents,
750 pub t_c: f64,
752 pub xi0: f64,
754 pub chi0: f64,
756 pub m0: f64,
758 pub c0: f64,
760}
761
762impl CriticalPhenomena {
763 #[allow(clippy::too_many_arguments)]
765 pub fn new(exp: CriticalExponents, t_c: f64, xi0: f64, chi0: f64, m0: f64, c0: f64) -> Self {
766 Self {
767 exp,
768 t_c,
769 xi0,
770 chi0,
771 m0,
772 c0,
773 }
774 }
775
776 pub fn reduced_temperature(&self, temp: f64) -> f64 {
778 (temp - self.t_c) / self.t_c
779 }
780
781 pub fn correlation_length(&self, temp: f64) -> f64 {
783 let t = self.reduced_temperature(temp);
784 if t.abs() < f64::EPSILON {
785 f64::INFINITY
786 } else {
787 self.xi0 * t.abs().powf(-self.exp.nu)
788 }
789 }
790
791 pub fn susceptibility(&self, temp: f64) -> f64 {
793 let t = self.reduced_temperature(temp);
794 if t.abs() < f64::EPSILON {
795 f64::INFINITY
796 } else {
797 self.chi0 * t.abs().powf(-self.exp.gamma)
798 }
799 }
800
801 pub fn order_parameter(&self, temp: f64) -> f64 {
803 let t = self.reduced_temperature(temp);
804 if t >= 0.0 {
805 0.0
806 } else {
807 self.m0 * (-t).powf(self.exp.beta_exp)
808 }
809 }
810
811 pub fn specific_heat(&self, temp: f64) -> f64 {
813 let t = self.reduced_temperature(temp);
814 if t.abs() < f64::EPSILON {
815 f64::INFINITY
816 } else {
817 self.c0 * t.abs().powf(-self.exp.alpha)
818 }
819 }
820
821 pub fn critical_isotherm(&self, h: f64) -> f64 {
823 h.abs().powf(1.0 / self.exp.delta) * h.signum()
824 }
825}
826
827#[derive(Debug, Clone)]
838pub struct DecimationTransformation {
839 pub k: f64,
841 pub ln_lambda: f64,
843 pub steps: usize,
845}
846
847impl DecimationTransformation {
848 pub fn new(k0: f64) -> Self {
850 Self {
851 k: k0,
852 ln_lambda: 0.0,
853 steps: 0,
854 }
855 }
856
857 pub fn step(&mut self) {
861 let two_k = 2.0 * self.k;
862 let cosh_2k = two_k.cosh();
863 self.k = 0.5 * cosh_2k.ln();
864 self.ln_lambda += 0.5 * (4.0 * cosh_2k).ln();
865 self.steps += 1;
866 }
867
868 pub fn run(&mut self, n: usize) {
870 for _ in 0..n {
871 self.step();
872 }
873 }
874
875 pub fn correlation_length(&self) -> f64 {
879 let th = self.k.tanh();
880 if th <= 0.0 || th >= 1.0 {
881 f64::INFINITY
882 } else {
883 -1.0 / th.ln()
884 }
885 }
886
887 pub fn trajectory(&self, n: usize) -> Vec<f64> {
889 let mut traj = Vec::with_capacity(n + 1);
890 let mut dec = self.clone();
891 traj.push(dec.k);
892 for _ in 0..n {
893 dec.step();
894 traj.push(dec.k);
895 }
896 traj
897 }
898
899 pub fn migdal_kadanoff_step(&mut self, b: u32) {
903 let bk = b as f64 * self.k;
904 self.k = 0.5 * (2.0 * bk).cosh().ln();
905 self.steps += 1;
906 }
907}
908
909#[derive(Debug, Clone)]
918pub struct ConformalFieldTheory {
919 pub c: f64,
921 pub primaries: Vec<(f64, i32)>,
923 pub name: String,
925}
926
927impl ConformalFieldTheory {
928 pub fn new(c: f64, name: impl Into<String>) -> Self {
930 Self {
931 c,
932 primaries: Vec::new(),
933 name: name.into(),
934 }
935 }
936
937 pub fn add_primary(&mut self, delta: f64, spin: i32) {
939 self.primaries.push((delta, spin));
940 }
941
942 pub fn casimir_energy(&self, l: f64) -> f64 {
944 -PI * self.c / (6.0 * l)
945 }
946
947 pub fn holomorphic_dimensions(&self) -> Vec<(f64, f64)> {
949 self.primaries
950 .iter()
951 .map(|&(delta, spin)| {
952 let h = (delta + spin as f64) / 2.0;
953 let hbar = (delta - spin as f64) / 2.0;
954 (h, hbar)
955 })
956 .collect()
957 }
958
959 pub fn virasoro_character(&self, h: f64, q: f64, n_terms: usize) -> f64 {
964 let mut eta_prod = 1.0_f64;
966 for n in 1..=n_terms {
967 eta_prod *= 1.0 - q.powi(n as i32);
968 }
969 let eta = q.powf(1.0 / 24.0) * eta_prod;
970 if eta.abs() < f64::EPSILON {
971 return 0.0;
972 }
973 q.powf(h - self.c / 24.0) / eta
974 }
975
976 pub fn s_matrix_wzw(&self, n: usize, m: usize, k: usize) -> f64 {
980 let kf = k as f64;
981 (2.0 / kf).sqrt() * (PI * n as f64 * m as f64 / kf).sin()
982 }
983
984 pub fn partition_function(&self, q: f64, n_terms: usize) -> f64 {
988 self.holomorphic_dimensions()
989 .iter()
990 .map(|&(h, hbar)| {
991 let chi_h = self.virasoro_character(h, q, n_terms);
992 let chi_hbar = self.virasoro_character(hbar, q, n_terms);
993 chi_h * chi_hbar
994 })
995 .sum()
996 }
997
998 pub fn central_charge_from_tt(&self, z: f64, tt_correlator: f64) -> f64 {
1002 2.0 * z.powi(4) * tt_correlator
1003 }
1004
1005 pub fn satisfies_unitarity_bound(&self) -> bool {
1007 self.primaries
1008 .iter()
1009 .all(|&(delta, spin)| delta >= spin.unsigned_abs() as f64)
1010 }
1011}
1012
1013#[cfg(test)]
1018mod tests {
1019 use super::*;
1020
1021 #[test]
1024 fn test_beta_one_loop_zero_at_origin() {
1025 let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "test");
1026 assert!((beta.evaluate(0.0)).abs() < 1e-15);
1027 }
1028
1029 #[test]
1030 fn test_beta_one_loop_negative_for_positive_coupling() {
1031 let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "QCD");
1032 assert!(beta.evaluate(0.5) < 0.0);
1034 }
1035
1036 #[test]
1037 fn test_beta_two_loop_fixed_point() {
1038 let beta = BetaFunction::new(1.0, -2.0, LoopOrder::TwoLoop, "WF");
1041 let gstar = beta.fixed_point().expect("expected fixed point");
1042 assert!((gstar - 0.5).abs() < 1e-12);
1043 }
1044
1045 #[test]
1046 fn test_beta_stability_exponent_sign() {
1047 let beta = BetaFunction::new(1.0, -2.0, LoopOrder::TwoLoop, "WF");
1048 let omega = beta.stability_exponent().expect("stability exponent");
1049 let gstar = 0.5_f64;
1051 let expected = -2.0 * 1.0 * gstar - 3.0 * (-2.0) * gstar * gstar;
1052 assert!((omega - expected).abs() < 1e-10);
1053 }
1054
1055 #[test]
1056 fn test_landau_pole() {
1057 let beta = BetaFunction::new(0.5, 0.0, LoopOrder::OneLoop, "phi4");
1058 let t_l = beta.landau_pole(1.0);
1060 assert!((t_l - 1.0).abs() < 1e-12);
1061 }
1062
1063 #[test]
1064 fn test_running_coupling_one_loop_approaches_zero() {
1065 let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "QCD");
1066 let g_inf = beta.running_coupling_one_loop(1.0, 1e6);
1067 assert!(g_inf < 0.01, "coupling should run to zero");
1068 }
1069
1070 #[test]
1071 fn test_run_coupling_euler_consistent() {
1072 let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "test");
1073 let g_euler = beta.run_coupling(0.5, 1.0, 10_000);
1074 let g_analytic = beta.running_coupling_one_loop(0.5, 1.0);
1075 assert!((g_euler - g_analytic).abs() < 1e-4);
1076 }
1077
1078 #[test]
1081 fn test_rg_flow_gamma_mass_quadratic() {
1082 let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "phi4");
1083 let flow = RgFlow::new(beta, 0.5, 0.1);
1084 assert!((flow.gamma_mass(2.0) - 0.5 * 4.0).abs() < 1e-12);
1086 }
1087
1088 #[test]
1089 fn test_rg_flow_integrate_mass_decreases() {
1090 let beta = BetaFunction::new(0.1, 0.0, LoopOrder::OneLoop, "test");
1091 let flow = RgFlow::new(beta, 0.2, 0.0);
1092 let result = flow.integrate(0.5, 1.0, 5.0, 1000);
1093 assert!(result.mass.last().unwrap().is_finite());
1095 }
1096
1097 #[test]
1098 fn test_rg_flow_nu_exponent() {
1099 let beta = BetaFunction::new(1.0, -2.0, LoopOrder::TwoLoop, "WF");
1101 let flow = RgFlow::new(beta, 1.0, 0.1);
1102 let nu = flow.nu_exponent().expect("nu exponent");
1103 assert!(nu > 0.0);
1104 }
1105
1106 #[test]
1109 fn test_wilson_fisher_fixed_point_n1() {
1110 let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
1112 let ustar = rg.wilson_fisher_fixed_point(1.0);
1113 let expected = 16.0 * PI * PI * 1.0 / 9.0;
1114 assert!((ustar - expected).abs() < 1e-10);
1115 }
1116
1117 #[test]
1118 fn test_eta_wf_n1_order_eps2() {
1119 let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
1120 let eta = rg.eta_wf(1.0);
1121 let expected = 1.0_f64.powi(2) / 54.0;
1123 assert!((eta - expected).abs() < 1e-10);
1124 }
1125
1126 #[test]
1127 fn test_nu_wf_n1() {
1128 let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
1130 let nu = rg.nu_wf(1.0);
1131 let expected = 1.0 / (2.0 - 1.0 / 3.0);
1132 assert!((nu - expected).abs() < 1e-10);
1133 }
1134
1135 #[test]
1136 fn test_block_spin_rescaling() {
1137 let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
1138 assert!((rg.rescale_correlation_length(10.0, 2.0) - 5.0).abs() < 1e-12);
1139 }
1140
1141 #[test]
1144 fn test_mean_field_exponents() {
1145 let exp = UniversalityClass::MeanField.exponents();
1146 assert!((exp.nu - 0.5).abs() < 1e-10);
1147 assert!((exp.beta_exp - 0.5).abs() < 1e-10);
1148 assert!((exp.gamma - 1.0).abs() < 1e-10);
1149 assert!((exp.delta - 3.0).abs() < 1e-10);
1150 }
1151
1152 #[test]
1153 fn test_ising_3d_exponents_reasonable() {
1154 let exp = UniversalityClass::Ising3D.exponents();
1155 assert!(exp.nu > 0.5 && exp.nu < 1.0);
1156 assert!(exp.eta > 0.0 && exp.eta < 0.1);
1157 }
1158
1159 #[test]
1160 fn test_n_components() {
1161 assert_eq!(UniversalityClass::Ising3D.n_components(), 1);
1162 assert_eq!(UniversalityClass::Xy3D.n_components(), 2);
1163 assert_eq!(UniversalityClass::Heisenberg3D.n_components(), 3);
1164 }
1165
1166 #[test]
1169 fn test_rushbrooke_mean_field() {
1170 let exp = UniversalityClass::MeanField.exponents();
1171 let sr = ScalingRelations::new(exp);
1172 let (lhs, rhs) = sr.rushbrooke();
1173 assert!((lhs - rhs).abs() < 1e-10);
1175 }
1176
1177 #[test]
1178 fn test_widom_mean_field() {
1179 let exp = UniversalityClass::MeanField.exponents();
1180 let sr = ScalingRelations::new(exp);
1181 let (lhs, rhs) = sr.widom();
1182 assert!((lhs - rhs).abs() < 1e-10);
1184 }
1185
1186 #[test]
1187 fn test_fisher_mean_field() {
1188 let exp = UniversalityClass::MeanField.exponents();
1189 let sr = ScalingRelations::new(exp);
1190 let (lhs, rhs) = sr.fisher();
1191 assert!((lhs - rhs).abs() < 1e-10);
1193 }
1194
1195 #[test]
1196 fn test_josephson_mean_field() {
1197 let exp = UniversalityClass::MeanField.exponents();
1198 let sr = ScalingRelations::new(exp);
1199 let (lhs, rhs) = sr.josephson();
1200 assert!((lhs - rhs).abs() < 1e-10);
1202 }
1203
1204 #[test]
1205 fn test_check_all_mean_field() {
1206 let exp = UniversalityClass::MeanField.exponents();
1207 let sr = ScalingRelations::new(exp);
1208 let (r, w, f, j) = sr.check_all(1e-10);
1209 assert!(r, "Rushbrooke failed");
1210 assert!(w, "Widom failed");
1211 assert!(f, "Fisher failed");
1212 assert!(j, "Josephson failed");
1213 }
1214
1215 #[test]
1218 fn test_renorm_coupling_msbar_finite() {
1219 let rc = RenormalizedCoupling::new(RenormalizationScheme::MSbar, 0.1, 1.0, 1);
1220 assert!(rc.g_renorm.is_finite());
1221 assert!(rc.g_renorm < rc.g_bare || (rc.g_renorm - rc.g_bare).abs() < rc.g_bare);
1222 }
1223
1224 #[test]
1225 fn test_renorm_coupling_run_msbar() {
1226 let rc = RenormalizedCoupling::new(RenormalizationScheme::MSbar, 0.3, 1.0, 1);
1227 let g2 = rc.run_msbar(10.0, 1.0);
1228 assert!(g2 < rc.g_renorm);
1230 }
1231
1232 #[test]
1235 fn test_ope_fusion_rule_exists() {
1236 let mut ope = OperatorProductExpansion::new();
1237 ope.add_coefficient(OpeCoefficient::new("σ", "σ", "ε", 0.5));
1238 assert!(ope.fusion_rule_exists("σ", "σ", "ε"));
1239 assert!(!ope.fusion_rule_exists("σ", "σ", "T"));
1240 }
1241
1242 #[test]
1243 fn test_conformal_block_2d_positive() {
1244 let ope = OperatorProductExpansion::new();
1245 let g = ope.conformal_block_2d(0.5, 0.25);
1246 assert!(g > 0.0);
1247 }
1248
1249 #[test]
1250 fn test_partial_wave_amplitude_positive() {
1251 let mut ope = OperatorProductExpansion::new();
1252 ope.add_operator("ε", 1.0);
1253 ope.add_coefficient(OpeCoefficient::new("σ", "σ", "ε", 0.8));
1254 let amp = ope.partial_wave_amplitude("σ", 0.1);
1255 assert!(amp > 0.0);
1256 }
1257
1258 #[test]
1261 fn test_correlation_length_diverges_at_tc() {
1262 let exp = UniversalityClass::MeanField.exponents();
1263 let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
1264 let xi = cp.correlation_length(2.269);
1265 assert!(xi.is_infinite());
1266 }
1267
1268 #[test]
1269 fn test_order_parameter_zero_above_tc() {
1270 let exp = UniversalityClass::Ising3D.exponents();
1271 let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
1272 assert_eq!(cp.order_parameter(3.0), 0.0);
1273 }
1274
1275 #[test]
1276 fn test_order_parameter_nonzero_below_tc() {
1277 let exp = UniversalityClass::Ising3D.exponents();
1278 let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
1279 assert!(cp.order_parameter(1.0) > 0.0);
1280 }
1281
1282 #[test]
1283 fn test_critical_isotherm_odd() {
1284 let exp = UniversalityClass::MeanField.exponents();
1285 let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
1286 let m_pos = cp.critical_isotherm(1.0);
1287 let m_neg = cp.critical_isotherm(-1.0);
1288 assert!((m_pos + m_neg).abs() < 1e-12);
1289 }
1290
1291 #[test]
1294 fn test_decimation_k_decreases_for_large_k() {
1295 let mut dec = DecimationTransformation::new(2.0);
1297 let k0 = dec.k;
1298 dec.step();
1299 assert!(dec.k < k0);
1301 }
1302
1303 #[test]
1304 fn test_decimation_runs_to_zero() {
1305 let mut dec = DecimationTransformation::new(1.0);
1306 dec.run(100);
1307 assert!(dec.k < 0.01);
1308 }
1309
1310 #[test]
1311 fn test_decimation_correlation_length_decreases() {
1312 let mut dec = DecimationTransformation::new(1.0);
1313 let xi0 = dec.correlation_length();
1314 dec.step();
1315 let xi1 = dec.correlation_length();
1316 assert!(xi1 < xi0);
1317 }
1318
1319 #[test]
1320 fn test_decimation_trajectory_length() {
1321 let dec = DecimationTransformation::new(1.0);
1322 let traj = dec.trajectory(10);
1323 assert_eq!(traj.len(), 11);
1324 }
1325
1326 #[test]
1329 fn test_casimir_energy_ising_cft() {
1330 let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
1332 cft.add_primary(0.0, 0); cft.add_primary(0.5, 0); cft.add_primary(1.0, 0); let e0 = cft.casimir_energy(1.0);
1336 let expected = -PI * 0.5 / 6.0;
1337 assert!((e0 - expected).abs() < 1e-12);
1338 }
1339
1340 #[test]
1341 fn test_holomorphic_dimensions_identity() {
1342 let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
1343 cft.add_primary(0.0, 0);
1344 let dims = cft.holomorphic_dimensions();
1345 assert!((dims[0].0).abs() < 1e-12);
1346 assert!((dims[0].1).abs() < 1e-12);
1347 }
1348
1349 #[test]
1350 fn test_unitarity_bound_satisfied() {
1351 let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
1352 cft.add_primary(0.0, 0);
1353 cft.add_primary(0.5, 0);
1354 cft.add_primary(1.0, 0);
1355 assert!(cft.satisfies_unitarity_bound());
1356 }
1357
1358 #[test]
1359 fn test_unitarity_bound_violated() {
1360 let mut cft = ConformalFieldTheory::new(1.0, "Ghost");
1361 cft.add_primary(0.3, 2); assert!(!cft.satisfies_unitarity_bound());
1363 }
1364
1365 #[test]
1366 fn test_virasoro_character_positive() {
1367 let cft = ConformalFieldTheory::new(0.5, "Ising2D");
1368 let chi = cft.virasoro_character(0.0, 0.9, 10);
1369 assert!(chi > 0.0);
1370 }
1371
1372 #[test]
1373 fn test_partition_function_positive() {
1374 let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
1375 cft.add_primary(0.0, 0);
1376 cft.add_primary(0.5, 0);
1377 cft.add_primary(1.0, 0);
1378 let z = cft.partition_function(0.5, 10);
1379 assert!(z >= 0.0);
1380 }
1381
1382 #[test]
1383 fn test_central_charge_from_tt() {
1384 let cft = ConformalFieldTheory::new(0.5, "Ising2D");
1385 let z = 2.0_f64;
1387 let tt = 0.5 / (2.0 * z.powi(4));
1388 let c = cft.central_charge_from_tt(z, tt);
1389 assert!((c - 0.5).abs() < 1e-10);
1390 }
1391
1392 #[test]
1393 fn test_s_matrix_wzw_symmetry() {
1394 let cft = ConformalFieldTheory::new(1.0, "SU2_k2");
1395 let s12 = cft.s_matrix_wzw(1, 2, 4);
1397 let s21 = cft.s_matrix_wzw(2, 1, 4);
1398 assert!((s12 - s21).abs() < 1e-12);
1399 }
1400
1401 #[test]
1402 fn test_rushbrooke_ising_3d() {
1403 let exp = UniversalityClass::Ising3D.exponents();
1404 let sr = ScalingRelations::new(exp);
1405 let (lhs, rhs) = sr.rushbrooke();
1406 assert!((lhs - rhs).abs() < 0.01, "lhs={lhs:.6}, rhs={rhs:.6}");
1408 }
1409
1410 #[test]
1411 fn test_migdal_kadanoff_step() {
1412 let mut dec = DecimationTransformation::new(1.0);
1413 let k0 = dec.k;
1414 dec.migdal_kadanoff_step(2);
1415 let expected = 0.5 * (2.0 * 2.0 * k0).cosh().ln();
1417 assert!((dec.k - expected).abs() < 1e-12);
1418 }
1419
1420 #[test]
1421 fn test_beta_no_fixed_point_one_loop() {
1422 let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "phi4");
1423 assert!(beta.fixed_point().is_none());
1424 }
1425
1426 #[test]
1427 fn test_wf_flow_u_approaches_fixed_point() {
1428 let mut rg = WilsonianRg::new(3.0, 1.0, 0.01, 0.0);
1429 let ustar = rg.wilson_fisher_fixed_point(1.0);
1430 rg.run(5000, 0.001, 1.0);
1431 assert!(
1434 (rg.u - ustar).abs() < 0.95 * ustar,
1435 "u={:.6}, u*={:.6}",
1436 rg.u,
1437 ustar
1438 );
1439 }
1440}