#![allow(dead_code)]
use std::f64::consts::PI;
struct Lcg {
state: u64,
}
impl Lcg {
fn new(seed: u64) -> Self {
Self { state: seed.max(1) }
}
fn next_u64(&mut self) -> u64 {
self.state = self
.state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
self.state
}
fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
}
}
pub const EULER_MASCHERONI: f64 = 0.577_215_664_901_532_9;
pub const FOUR_PI_SQ: f64 = 4.0 * PI * PI;
pub const EPSILON_WILSON: f64 = 1.0;
pub const DEFAULT_UV_CUTOFF: f64 = 1.0e3;
pub const FP_TOL: f64 = 1.0e-10;
pub const FP_MAX_ITER: usize = 1_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopOrder {
OneLoop,
TwoLoop,
}
#[derive(Debug, Clone)]
pub struct BetaFunction {
pub b0: f64,
pub b1: f64,
pub order: LoopOrder,
pub name: String,
}
impl BetaFunction {
pub fn new(b0: f64, b1: f64, order: LoopOrder, name: impl Into<String>) -> Self {
Self {
b0,
b1,
order,
name: name.into(),
}
}
pub fn evaluate(&self, g: f64) -> f64 {
let one_loop = -self.b0 * g * g;
match self.order {
LoopOrder::OneLoop => one_loop,
LoopOrder::TwoLoop => one_loop - self.b1 * g * g * g,
}
}
pub fn derivative(&self, g: f64) -> f64 {
let one_loop = -2.0 * self.b0 * g;
match self.order {
LoopOrder::OneLoop => one_loop,
LoopOrder::TwoLoop => one_loop - 3.0 * self.b1 * g * g,
}
}
pub fn fixed_point(&self) -> Option<f64> {
match self.order {
LoopOrder::OneLoop => {
None
}
LoopOrder::TwoLoop => {
if self.b1.abs() < f64::EPSILON {
None
} else {
let gstar = -self.b0 / self.b1;
if gstar > 0.0 { Some(gstar) } else { None }
}
}
}
}
pub fn stability_exponent(&self) -> Option<f64> {
self.fixed_point().map(|gstar| self.derivative(gstar))
}
pub fn run_coupling(&self, g0: f64, t_final: f64, steps: usize) -> f64 {
let dt = t_final / steps as f64;
let mut g = g0;
for _ in 0..steps {
g += self.evaluate(g) * dt;
}
g
}
pub fn running_coupling_one_loop(&self, g0: f64, t: f64) -> f64 {
let denom = 1.0 + self.b0 * g0 * t;
if denom <= 0.0 {
f64::INFINITY
} else {
g0 / denom
}
}
pub fn landau_pole(&self, g0: f64) -> f64 {
if self.b0 <= 0.0 {
f64::INFINITY
} else {
1.0 / (2.0 * self.b0 * g0)
}
}
}
#[derive(Debug, Clone)]
pub struct RgFlowResult {
pub t_values: Vec<f64>,
pub coupling: Vec<f64>,
pub mass: Vec<f64>,
pub eta: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct RgFlow {
pub beta: BetaFunction,
pub gamma0: f64,
pub eta0: f64,
}
impl RgFlow {
pub fn new(beta: BetaFunction, gamma0: f64, eta0: f64) -> Self {
Self { beta, gamma0, eta0 }
}
pub fn gamma_mass(&self, g: f64) -> f64 {
self.gamma0 * g * g
}
pub fn eta(&self, g: f64) -> f64 {
self.eta0 * g * g
}
pub fn integrate(&self, g0: f64, m0: f64, t_max: f64, n: usize) -> RgFlowResult {
let dt = t_max / n as f64;
let mut t_values = Vec::with_capacity(n + 1);
let mut coupling = Vec::with_capacity(n + 1);
let mut mass = Vec::with_capacity(n + 1);
let mut eta = Vec::with_capacity(n + 1);
let mut g = g0;
let mut m = m0;
let mut t = 0.0;
t_values.push(t);
coupling.push(g);
mass.push(m);
eta.push(self.eta(g));
for _ in 0..n {
let dg = self.beta.evaluate(g) * dt;
let dm = -self.gamma_mass(g) * m * dt;
g += dg;
m += dm;
t += dt;
t_values.push(t);
coupling.push(g);
mass.push(m);
eta.push(self.eta(g));
}
RgFlowResult {
t_values,
coupling,
mass,
eta,
}
}
pub fn nu_exponent(&self) -> Option<f64> {
self.beta.stability_exponent().and_then(|omega| {
if omega.abs() < f64::EPSILON {
None
} else {
Some(1.0 / omega.abs())
}
})
}
}
#[derive(Debug, Clone)]
pub struct WilsonianRg {
pub d: f64,
pub epsilon: f64,
pub lambda: f64,
pub step: usize,
pub u: f64,
pub r: f64,
}
impl WilsonianRg {
pub fn new(d: f64, lambda: f64, u: f64, r: f64) -> Self {
Self {
d,
epsilon: 4.0 - d,
lambda,
step: 0,
u,
r,
}
}
#[allow(clippy::too_many_arguments)]
pub fn step_flow(&mut self, dl: f64, n_components: f64) {
let du = self.epsilon * self.u - (n_components + 8.0) / (16.0 * PI * PI) * self.u * self.u;
let dr = 2.0 * self.r + (n_components + 2.0) / (16.0 * PI * PI) * self.u;
self.u += du * dl;
self.r += dr * dl;
self.step += 1;
}
pub fn wilson_fisher_fixed_point(&self, n_components: f64) -> f64 {
16.0 * PI * PI * self.epsilon / (n_components + 8.0)
}
pub fn eta_wf(&self, n_components: f64) -> f64 {
let eps = self.epsilon;
(n_components + 2.0) * eps * eps / (2.0 * (n_components + 8.0).powi(2))
}
pub fn nu_wf(&self, n_components: f64) -> f64 {
let inv_nu = 2.0 - (n_components + 2.0) / (n_components + 8.0) * self.epsilon;
1.0 / inv_nu
}
pub fn rescale_correlation_length(&self, xi: f64, b: f64) -> f64 {
xi / b
}
pub fn run(&mut self, n_steps: usize, dl: f64, n_components: f64) {
for _ in 0..n_steps {
self.step_flow(dl, n_components);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UniversalityClass {
Ising2D,
Ising3D,
Xy3D,
Heisenberg3D,
MeanField,
}
#[derive(Debug, Clone)]
pub struct CriticalExponents {
pub nu: f64,
pub eta: f64,
pub beta_exp: f64,
pub gamma: f64,
pub alpha: f64,
pub delta: f64,
pub d: f64,
}
impl UniversalityClass {
pub fn exponents(self) -> CriticalExponents {
match self {
UniversalityClass::Ising2D => CriticalExponents {
nu: 1.0,
eta: 0.25,
beta_exp: 0.125,
gamma: 1.75,
alpha: 0.0, delta: 15.0,
d: 2.0,
},
UniversalityClass::Ising3D => CriticalExponents {
nu: 0.6301,
eta: 0.0364,
beta_exp: 0.3265,
gamma: 1.2372,
alpha: 0.1100,
delta: 4.789,
d: 3.0,
},
UniversalityClass::Xy3D => CriticalExponents {
nu: 0.6717,
eta: 0.0381,
beta_exp: 0.3486,
gamma: 1.3178,
alpha: -0.0151,
delta: 4.780,
d: 3.0,
},
UniversalityClass::Heisenberg3D => CriticalExponents {
nu: 0.7112,
eta: 0.0375,
beta_exp: 0.3689,
gamma: 1.3960,
alpha: -0.1336,
delta: 4.783,
d: 3.0,
},
UniversalityClass::MeanField => CriticalExponents {
nu: 0.5,
eta: 0.0,
beta_exp: 0.5,
gamma: 1.0,
alpha: 0.0,
delta: 3.0,
d: 4.0, },
}
}
pub fn n_components(self) -> usize {
match self {
UniversalityClass::Ising2D | UniversalityClass::Ising3D => 1,
UniversalityClass::Xy3D => 2,
UniversalityClass::Heisenberg3D => 3,
UniversalityClass::MeanField => 1,
}
}
pub fn dimension(self) -> f64 {
match self {
UniversalityClass::Ising2D => 2.0,
_ => 3.0,
}
}
}
#[derive(Debug, Clone)]
pub struct ScalingRelations {
pub exp: CriticalExponents,
}
impl ScalingRelations {
pub fn new(exp: CriticalExponents) -> Self {
Self { exp }
}
pub fn rushbrooke(&self) -> (f64, f64) {
let lhs = self.exp.alpha + 2.0 * self.exp.beta_exp + self.exp.gamma;
(lhs, 2.0)
}
pub fn widom(&self) -> (f64, f64) {
let lhs = self.exp.gamma;
let rhs = self.exp.beta_exp * (self.exp.delta - 1.0);
(lhs, rhs)
}
pub fn fisher(&self) -> (f64, f64) {
let lhs = self.exp.gamma;
let rhs = (2.0 - self.exp.eta) * self.exp.nu;
(lhs, rhs)
}
pub fn josephson(&self) -> (f64, f64) {
let lhs = 2.0 - self.exp.alpha;
let rhs = self.exp.d * self.exp.nu;
(lhs, rhs)
}
pub fn check_all(&self, tol: f64) -> (bool, bool, bool, bool) {
let (r_lhs, r_rhs) = self.rushbrooke();
let (w_lhs, w_rhs) = self.widom();
let (f_lhs, f_rhs) = self.fisher();
let (j_lhs, j_rhs) = self.josephson();
(
(r_lhs - r_rhs).abs() < tol,
(w_lhs - w_rhs).abs() < tol,
(f_lhs - f_rhs).abs() < tol,
(j_lhs - j_rhs).abs() < tol,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenormalizationScheme {
MSbar,
MomentumSubtraction,
OnShell,
}
#[derive(Debug, Clone)]
pub struct RenormalizedCoupling {
pub scheme: RenormalizationScheme,
pub g_bare: f64,
pub g_renorm: f64,
pub mu: f64,
pub divergence_degree: i32,
}
impl RenormalizedCoupling {
pub fn new(
scheme: RenormalizationScheme,
g_bare: f64,
mu: f64,
divergence_degree: i32,
) -> Self {
let g_renorm = match scheme {
RenormalizationScheme::MSbar => {
let log_factor = (4.0 * PI).ln() - EULER_MASCHERONI;
g_bare - g_bare * g_bare * log_factor / (16.0 * PI * PI)
}
RenormalizationScheme::MomentumSubtraction => {
g_bare - g_bare * g_bare * mu.ln() / (16.0 * PI * PI)
}
RenormalizationScheme::OnShell => {
g_bare * (1.0 - g_bare / (16.0 * PI * PI))
}
};
Self {
scheme,
g_bare,
g_renorm,
mu,
divergence_degree,
}
}
pub fn convert_msbar_to_mom(&self, delta: f64) -> f64 {
self.g_renorm * (1.0 + delta * self.g_renorm / (16.0 * PI * PI))
}
pub fn run_msbar(&self, mu2: f64, b0: f64) -> f64 {
let t = (mu2 / self.mu).ln();
self.g_renorm / (1.0 + 2.0 * b0 * self.g_renorm * t).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct OpeCoefficient {
pub label_i: String,
pub label_j: String,
pub label_k: String,
pub value: f64,
}
impl OpeCoefficient {
pub fn new(
label_i: impl Into<String>,
label_j: impl Into<String>,
label_k: impl Into<String>,
value: f64,
) -> Self {
Self {
label_i: label_i.into(),
label_j: label_j.into(),
label_k: label_k.into(),
value,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct OperatorProductExpansion {
pub coefficients: Vec<OpeCoefficient>,
pub dimensions: Vec<(String, f64)>,
}
impl OperatorProductExpansion {
pub fn new() -> Self {
Self::default()
}
pub fn add_coefficient(&mut self, coeff: OpeCoefficient) {
self.coefficients.push(coeff);
}
pub fn add_operator(&mut self, label: impl Into<String>, delta: f64) {
self.dimensions.push((label.into(), delta));
}
pub fn conformal_block_2d(&self, delta: f64, z: f64) -> f64 {
if z <= 0.0 {
return 0.0;
}
let z_pow = z.powf(delta / 2.0);
let correction = 1.0 + delta * z / (2.0 * (2.0 * delta + 1.0));
z_pow * correction
}
pub fn partial_wave_amplitude(&self, sigma_label: &str, z: f64) -> f64 {
let mut amplitude = 0.0;
for coeff in &self.coefficients {
if coeff.label_i == sigma_label
&& coeff.label_j == sigma_label
&& let Some((_, delta_k)) = self
.dimensions
.iter()
.find(|(lbl, _)| lbl == &coeff.label_k)
{
let g = self.conformal_block_2d(*delta_k, z);
amplitude += coeff.value * coeff.value * g;
}
}
amplitude
}
pub fn fusion_rule_exists(&self, label_i: &str, label_j: &str, label_k: &str) -> bool {
self.coefficients
.iter()
.any(|c| c.label_i == label_i && c.label_j == label_j && c.label_k == label_k)
}
}
#[derive(Debug, Clone)]
pub struct CriticalPhenomena {
pub exp: CriticalExponents,
pub t_c: f64,
pub xi0: f64,
pub chi0: f64,
pub m0: f64,
pub c0: f64,
}
impl CriticalPhenomena {
#[allow(clippy::too_many_arguments)]
pub fn new(exp: CriticalExponents, t_c: f64, xi0: f64, chi0: f64, m0: f64, c0: f64) -> Self {
Self {
exp,
t_c,
xi0,
chi0,
m0,
c0,
}
}
pub fn reduced_temperature(&self, temp: f64) -> f64 {
(temp - self.t_c) / self.t_c
}
pub fn correlation_length(&self, temp: f64) -> f64 {
let t = self.reduced_temperature(temp);
if t.abs() < f64::EPSILON {
f64::INFINITY
} else {
self.xi0 * t.abs().powf(-self.exp.nu)
}
}
pub fn susceptibility(&self, temp: f64) -> f64 {
let t = self.reduced_temperature(temp);
if t.abs() < f64::EPSILON {
f64::INFINITY
} else {
self.chi0 * t.abs().powf(-self.exp.gamma)
}
}
pub fn order_parameter(&self, temp: f64) -> f64 {
let t = self.reduced_temperature(temp);
if t >= 0.0 {
0.0
} else {
self.m0 * (-t).powf(self.exp.beta_exp)
}
}
pub fn specific_heat(&self, temp: f64) -> f64 {
let t = self.reduced_temperature(temp);
if t.abs() < f64::EPSILON {
f64::INFINITY
} else {
self.c0 * t.abs().powf(-self.exp.alpha)
}
}
pub fn critical_isotherm(&self, h: f64) -> f64 {
h.abs().powf(1.0 / self.exp.delta) * h.signum()
}
}
#[derive(Debug, Clone)]
pub struct DecimationTransformation {
pub k: f64,
pub ln_lambda: f64,
pub steps: usize,
}
impl DecimationTransformation {
pub fn new(k0: f64) -> Self {
Self {
k: k0,
ln_lambda: 0.0,
steps: 0,
}
}
pub fn step(&mut self) {
let two_k = 2.0 * self.k;
let cosh_2k = two_k.cosh();
self.k = 0.5 * cosh_2k.ln();
self.ln_lambda += 0.5 * (4.0 * cosh_2k).ln();
self.steps += 1;
}
pub fn run(&mut self, n: usize) {
for _ in 0..n {
self.step();
}
}
pub fn correlation_length(&self) -> f64 {
let th = self.k.tanh();
if th <= 0.0 || th >= 1.0 {
f64::INFINITY
} else {
-1.0 / th.ln()
}
}
pub fn trajectory(&self, n: usize) -> Vec<f64> {
let mut traj = Vec::with_capacity(n + 1);
let mut dec = self.clone();
traj.push(dec.k);
for _ in 0..n {
dec.step();
traj.push(dec.k);
}
traj
}
pub fn migdal_kadanoff_step(&mut self, b: u32) {
let bk = b as f64 * self.k;
self.k = 0.5 * (2.0 * bk).cosh().ln();
self.steps += 1;
}
}
#[derive(Debug, Clone)]
pub struct ConformalFieldTheory {
pub c: f64,
pub primaries: Vec<(f64, i32)>,
pub name: String,
}
impl ConformalFieldTheory {
pub fn new(c: f64, name: impl Into<String>) -> Self {
Self {
c,
primaries: Vec::new(),
name: name.into(),
}
}
pub fn add_primary(&mut self, delta: f64, spin: i32) {
self.primaries.push((delta, spin));
}
pub fn casimir_energy(&self, l: f64) -> f64 {
-PI * self.c / (6.0 * l)
}
pub fn holomorphic_dimensions(&self) -> Vec<(f64, f64)> {
self.primaries
.iter()
.map(|&(delta, spin)| {
let h = (delta + spin as f64) / 2.0;
let hbar = (delta - spin as f64) / 2.0;
(h, hbar)
})
.collect()
}
pub fn virasoro_character(&self, h: f64, q: f64, n_terms: usize) -> f64 {
let mut eta_prod = 1.0_f64;
for n in 1..=n_terms {
eta_prod *= 1.0 - q.powi(n as i32);
}
let eta = q.powf(1.0 / 24.0) * eta_prod;
if eta.abs() < f64::EPSILON {
return 0.0;
}
q.powf(h - self.c / 24.0) / eta
}
pub fn s_matrix_wzw(&self, n: usize, m: usize, k: usize) -> f64 {
let kf = k as f64;
(2.0 / kf).sqrt() * (PI * n as f64 * m as f64 / kf).sin()
}
pub fn partition_function(&self, q: f64, n_terms: usize) -> f64 {
self.holomorphic_dimensions()
.iter()
.map(|&(h, hbar)| {
let chi_h = self.virasoro_character(h, q, n_terms);
let chi_hbar = self.virasoro_character(hbar, q, n_terms);
chi_h * chi_hbar
})
.sum()
}
pub fn central_charge_from_tt(&self, z: f64, tt_correlator: f64) -> f64 {
2.0 * z.powi(4) * tt_correlator
}
pub fn satisfies_unitarity_bound(&self) -> bool {
self.primaries
.iter()
.all(|&(delta, spin)| delta >= spin.unsigned_abs() as f64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_beta_one_loop_zero_at_origin() {
let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "test");
assert!((beta.evaluate(0.0)).abs() < 1e-15);
}
#[test]
fn test_beta_one_loop_negative_for_positive_coupling() {
let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "QCD");
assert!(beta.evaluate(0.5) < 0.0);
}
#[test]
fn test_beta_two_loop_fixed_point() {
let beta = BetaFunction::new(1.0, -2.0, LoopOrder::TwoLoop, "WF");
let gstar = beta.fixed_point().expect("expected fixed point");
assert!((gstar - 0.5).abs() < 1e-12);
}
#[test]
fn test_beta_stability_exponent_sign() {
let beta = BetaFunction::new(1.0, -2.0, LoopOrder::TwoLoop, "WF");
let omega = beta.stability_exponent().expect("stability exponent");
let gstar = 0.5_f64;
let expected = -2.0 * 1.0 * gstar - 3.0 * (-2.0) * gstar * gstar;
assert!((omega - expected).abs() < 1e-10);
}
#[test]
fn test_landau_pole() {
let beta = BetaFunction::new(0.5, 0.0, LoopOrder::OneLoop, "phi4");
let t_l = beta.landau_pole(1.0);
assert!((t_l - 1.0).abs() < 1e-12);
}
#[test]
fn test_running_coupling_one_loop_approaches_zero() {
let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "QCD");
let g_inf = beta.running_coupling_one_loop(1.0, 1e6);
assert!(g_inf < 0.01, "coupling should run to zero");
}
#[test]
fn test_run_coupling_euler_consistent() {
let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "test");
let g_euler = beta.run_coupling(0.5, 1.0, 10_000);
let g_analytic = beta.running_coupling_one_loop(0.5, 1.0);
assert!((g_euler - g_analytic).abs() < 1e-4);
}
#[test]
fn test_rg_flow_gamma_mass_quadratic() {
let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "phi4");
let flow = RgFlow::new(beta, 0.5, 0.1);
assert!((flow.gamma_mass(2.0) - 0.5 * 4.0).abs() < 1e-12);
}
#[test]
fn test_rg_flow_integrate_mass_decreases() {
let beta = BetaFunction::new(0.1, 0.0, LoopOrder::OneLoop, "test");
let flow = RgFlow::new(beta, 0.2, 0.0);
let result = flow.integrate(0.5, 1.0, 5.0, 1000);
assert!(result.mass.last().unwrap().is_finite());
}
#[test]
fn test_rg_flow_nu_exponent() {
let beta = BetaFunction::new(1.0, -2.0, LoopOrder::TwoLoop, "WF");
let flow = RgFlow::new(beta, 1.0, 0.1);
let nu = flow.nu_exponent().expect("nu exponent");
assert!(nu > 0.0);
}
#[test]
fn test_wilson_fisher_fixed_point_n1() {
let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
let ustar = rg.wilson_fisher_fixed_point(1.0);
let expected = 16.0 * PI * PI * 1.0 / 9.0;
assert!((ustar - expected).abs() < 1e-10);
}
#[test]
fn test_eta_wf_n1_order_eps2() {
let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
let eta = rg.eta_wf(1.0);
let expected = 1.0_f64.powi(2) / 54.0;
assert!((eta - expected).abs() < 1e-10);
}
#[test]
fn test_nu_wf_n1() {
let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
let nu = rg.nu_wf(1.0);
let expected = 1.0 / (2.0 - 1.0 / 3.0);
assert!((nu - expected).abs() < 1e-10);
}
#[test]
fn test_block_spin_rescaling() {
let rg = WilsonianRg::new(3.0, 1.0, 0.1, 0.0);
assert!((rg.rescale_correlation_length(10.0, 2.0) - 5.0).abs() < 1e-12);
}
#[test]
fn test_mean_field_exponents() {
let exp = UniversalityClass::MeanField.exponents();
assert!((exp.nu - 0.5).abs() < 1e-10);
assert!((exp.beta_exp - 0.5).abs() < 1e-10);
assert!((exp.gamma - 1.0).abs() < 1e-10);
assert!((exp.delta - 3.0).abs() < 1e-10);
}
#[test]
fn test_ising_3d_exponents_reasonable() {
let exp = UniversalityClass::Ising3D.exponents();
assert!(exp.nu > 0.5 && exp.nu < 1.0);
assert!(exp.eta > 0.0 && exp.eta < 0.1);
}
#[test]
fn test_n_components() {
assert_eq!(UniversalityClass::Ising3D.n_components(), 1);
assert_eq!(UniversalityClass::Xy3D.n_components(), 2);
assert_eq!(UniversalityClass::Heisenberg3D.n_components(), 3);
}
#[test]
fn test_rushbrooke_mean_field() {
let exp = UniversalityClass::MeanField.exponents();
let sr = ScalingRelations::new(exp);
let (lhs, rhs) = sr.rushbrooke();
assert!((lhs - rhs).abs() < 1e-10);
}
#[test]
fn test_widom_mean_field() {
let exp = UniversalityClass::MeanField.exponents();
let sr = ScalingRelations::new(exp);
let (lhs, rhs) = sr.widom();
assert!((lhs - rhs).abs() < 1e-10);
}
#[test]
fn test_fisher_mean_field() {
let exp = UniversalityClass::MeanField.exponents();
let sr = ScalingRelations::new(exp);
let (lhs, rhs) = sr.fisher();
assert!((lhs - rhs).abs() < 1e-10);
}
#[test]
fn test_josephson_mean_field() {
let exp = UniversalityClass::MeanField.exponents();
let sr = ScalingRelations::new(exp);
let (lhs, rhs) = sr.josephson();
assert!((lhs - rhs).abs() < 1e-10);
}
#[test]
fn test_check_all_mean_field() {
let exp = UniversalityClass::MeanField.exponents();
let sr = ScalingRelations::new(exp);
let (r, w, f, j) = sr.check_all(1e-10);
assert!(r, "Rushbrooke failed");
assert!(w, "Widom failed");
assert!(f, "Fisher failed");
assert!(j, "Josephson failed");
}
#[test]
fn test_renorm_coupling_msbar_finite() {
let rc = RenormalizedCoupling::new(RenormalizationScheme::MSbar, 0.1, 1.0, 1);
assert!(rc.g_renorm.is_finite());
assert!(rc.g_renorm < rc.g_bare || (rc.g_renorm - rc.g_bare).abs() < rc.g_bare);
}
#[test]
fn test_renorm_coupling_run_msbar() {
let rc = RenormalizedCoupling::new(RenormalizationScheme::MSbar, 0.3, 1.0, 1);
let g2 = rc.run_msbar(10.0, 1.0);
assert!(g2 < rc.g_renorm);
}
#[test]
fn test_ope_fusion_rule_exists() {
let mut ope = OperatorProductExpansion::new();
ope.add_coefficient(OpeCoefficient::new("σ", "σ", "ε", 0.5));
assert!(ope.fusion_rule_exists("σ", "σ", "ε"));
assert!(!ope.fusion_rule_exists("σ", "σ", "T"));
}
#[test]
fn test_conformal_block_2d_positive() {
let ope = OperatorProductExpansion::new();
let g = ope.conformal_block_2d(0.5, 0.25);
assert!(g > 0.0);
}
#[test]
fn test_partial_wave_amplitude_positive() {
let mut ope = OperatorProductExpansion::new();
ope.add_operator("ε", 1.0);
ope.add_coefficient(OpeCoefficient::new("σ", "σ", "ε", 0.8));
let amp = ope.partial_wave_amplitude("σ", 0.1);
assert!(amp > 0.0);
}
#[test]
fn test_correlation_length_diverges_at_tc() {
let exp = UniversalityClass::MeanField.exponents();
let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
let xi = cp.correlation_length(2.269);
assert!(xi.is_infinite());
}
#[test]
fn test_order_parameter_zero_above_tc() {
let exp = UniversalityClass::Ising3D.exponents();
let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
assert_eq!(cp.order_parameter(3.0), 0.0);
}
#[test]
fn test_order_parameter_nonzero_below_tc() {
let exp = UniversalityClass::Ising3D.exponents();
let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
assert!(cp.order_parameter(1.0) > 0.0);
}
#[test]
fn test_critical_isotherm_odd() {
let exp = UniversalityClass::MeanField.exponents();
let cp = CriticalPhenomena::new(exp, 2.269, 1.0, 1.0, 1.0, 1.0);
let m_pos = cp.critical_isotherm(1.0);
let m_neg = cp.critical_isotherm(-1.0);
assert!((m_pos + m_neg).abs() < 1e-12);
}
#[test]
fn test_decimation_k_decreases_for_large_k() {
let mut dec = DecimationTransformation::new(2.0);
let k0 = dec.k;
dec.step();
assert!(dec.k < k0);
}
#[test]
fn test_decimation_runs_to_zero() {
let mut dec = DecimationTransformation::new(1.0);
dec.run(100);
assert!(dec.k < 0.01);
}
#[test]
fn test_decimation_correlation_length_decreases() {
let mut dec = DecimationTransformation::new(1.0);
let xi0 = dec.correlation_length();
dec.step();
let xi1 = dec.correlation_length();
assert!(xi1 < xi0);
}
#[test]
fn test_decimation_trajectory_length() {
let dec = DecimationTransformation::new(1.0);
let traj = dec.trajectory(10);
assert_eq!(traj.len(), 11);
}
#[test]
fn test_casimir_energy_ising_cft() {
let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
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);
let expected = -PI * 0.5 / 6.0;
assert!((e0 - expected).abs() < 1e-12);
}
#[test]
fn test_holomorphic_dimensions_identity() {
let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
cft.add_primary(0.0, 0);
let dims = cft.holomorphic_dimensions();
assert!((dims[0].0).abs() < 1e-12);
assert!((dims[0].1).abs() < 1e-12);
}
#[test]
fn test_unitarity_bound_satisfied() {
let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
cft.add_primary(0.0, 0);
cft.add_primary(0.5, 0);
cft.add_primary(1.0, 0);
assert!(cft.satisfies_unitarity_bound());
}
#[test]
fn test_unitarity_bound_violated() {
let mut cft = ConformalFieldTheory::new(1.0, "Ghost");
cft.add_primary(0.3, 2); assert!(!cft.satisfies_unitarity_bound());
}
#[test]
fn test_virasoro_character_positive() {
let cft = ConformalFieldTheory::new(0.5, "Ising2D");
let chi = cft.virasoro_character(0.0, 0.9, 10);
assert!(chi > 0.0);
}
#[test]
fn test_partition_function_positive() {
let mut cft = ConformalFieldTheory::new(0.5, "Ising2D");
cft.add_primary(0.0, 0);
cft.add_primary(0.5, 0);
cft.add_primary(1.0, 0);
let z = cft.partition_function(0.5, 10);
assert!(z >= 0.0);
}
#[test]
fn test_central_charge_from_tt() {
let cft = ConformalFieldTheory::new(0.5, "Ising2D");
let z = 2.0_f64;
let tt = 0.5 / (2.0 * z.powi(4));
let c = cft.central_charge_from_tt(z, tt);
assert!((c - 0.5).abs() < 1e-10);
}
#[test]
fn test_s_matrix_wzw_symmetry() {
let cft = ConformalFieldTheory::new(1.0, "SU2_k2");
let s12 = cft.s_matrix_wzw(1, 2, 4);
let s21 = cft.s_matrix_wzw(2, 1, 4);
assert!((s12 - s21).abs() < 1e-12);
}
#[test]
fn test_rushbrooke_ising_3d() {
let exp = UniversalityClass::Ising3D.exponents();
let sr = ScalingRelations::new(exp);
let (lhs, rhs) = sr.rushbrooke();
assert!((lhs - rhs).abs() < 0.01, "lhs={lhs:.6}, rhs={rhs:.6}");
}
#[test]
fn test_migdal_kadanoff_step() {
let mut dec = DecimationTransformation::new(1.0);
let k0 = dec.k;
dec.migdal_kadanoff_step(2);
let expected = 0.5 * (2.0 * 2.0 * k0).cosh().ln();
assert!((dec.k - expected).abs() < 1e-12);
}
#[test]
fn test_beta_no_fixed_point_one_loop() {
let beta = BetaFunction::new(1.0, 0.0, LoopOrder::OneLoop, "phi4");
assert!(beta.fixed_point().is_none());
}
#[test]
fn test_wf_flow_u_approaches_fixed_point() {
let mut rg = WilsonianRg::new(3.0, 1.0, 0.01, 0.0);
let ustar = rg.wilson_fisher_fixed_point(1.0);
rg.run(5000, 0.001, 1.0);
assert!(
(rg.u - ustar).abs() < 0.95 * ustar,
"u={:.6}, u*={:.6}",
rg.u,
ustar
);
}
}