Skip to main content

renormalization_group/
critical_surface.rs

1//! # Critical Surface Identification
2//!
3//! Identifies critical manifolds separating fleet phases
4//! (ordered/disordered/critical). The critical surface has codimension
5//! equal to the number of relevant operators at the critical fixed point.
6
7use crate::flow::FlowEquation;
8use crate::fixed_point::{FixedPoint, analyze_stability};
9use serde::{Deserialize, Serialize};
10
11/// A fleet phase.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum FleetPhase {
14    /// Ordered phase: agents are aligned
15    Ordered,
16    /// Disordered phase: agents are random
17    Disordered,
18    /// Critical: on the boundary between phases
19    Critical,
20}
21
22/// A critical surface (manifold) in parameter space.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct CriticalSurface {
25    /// The fixed point this surface is associated with
26    pub fixed_point: FixedPoint,
27    /// Number of relevant directions (codimension of the surface)
28    pub codimension: usize,
29    /// Description of the surface
30    pub description: String,
31}
32
33/// Result of a phase determination.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct PhaseResult {
36    /// The identified phase
37    pub phase: FleetPhase,
38    /// Distance to the nearest critical surface (if known)
39    pub distance_to_critical: Option<f64>,
40    /// Order parameter value
41    pub order_parameter: f64,
42}
43
44/// Determine the fleet phase by flowing to the IR fixed point
45/// and checking which basin of attraction the flow ends up in.
46pub fn determine_phase(
47    flow_eq: &dyn FlowEquation,
48    initial_params: &[f64],
49    order_param_index: usize,
50    n_steps: usize,
51) -> PhaseResult {
52    let mut current = initial_params.to_vec();
53    let _dim = flow_eq.dim();
54
55    // Flow for n_steps
56    for _ in 0..n_steps {
57        current = flow_eq.flow_step(&current, 0);
58    }
59
60    // Check if the flow diverged (ordered) or went to zero (disordered)
61    let magnitude: f64 = current.iter().map(|x| x * x).sum::<f64>().sqrt();
62    let order_param = if order_param_index < current.len() {
63        current[order_param_index]
64    } else {
65        magnitude
66    };
67
68    let phase = if magnitude > 1e10 {
69        FleetPhase::Ordered
70    } else if magnitude < 1e-3 {
71        FleetPhase::Disordered
72    } else {
73        FleetPhase::Critical
74    };
75
76    PhaseResult {
77        phase,
78        distance_to_critical: None,
79        order_parameter: order_param.abs(),
80    }
81}
82
83/// Compute the critical surface for a given fixed point.
84/// The critical surface is the set of points that flow to the fixed point.
85/// Its codimension equals the number of relevant operators.
86pub fn compute_critical_surface(
87    flow_eq: &dyn FlowEquation,
88    fixed_point_location: &[f64],
89) -> CriticalSurface {
90    let fp = analyze_stability(flow_eq, fixed_point_location);
91    CriticalSurface {
92        codimension: fp.n_relevant(),
93        fixed_point: fp.clone(),
94        description: format!(
95            "Critical surface with codimension {} at {:?}",
96            fp.n_relevant(),
97            fixed_point_location
98        ),
99    }
100}
101
102/// Bisection to find the critical point along a line in parameter space.
103/// Given two endpoints (one ordered, one disordered), find where the phase boundary is.
104pub fn find_critical_point(
105    flow_eq: &dyn FlowEquation,
106    ordered_params: &[f64],
107    disordered_params: &[f64],
108    order_param_index: usize,
109    n_steps: usize,
110    tolerance: f64,
111    max_iter: usize,
112) -> Vec<f64> {
113    let mut lo = ordered_params.to_vec();
114    let mut hi = disordered_params.to_vec();
115    let dim = lo.len();
116
117    for _ in 0..max_iter {
118        let mid: Vec<f64> = (0..dim).map(|i| 0.5 * (lo[i] + hi[i])).collect();
119        let mut hi_diff = 0.0;
120        for i in 0..dim {
121            hi_diff += (hi[i] - lo[i]).powi(2);
122        }
123        if hi_diff.sqrt() < tolerance {
124            return mid;
125        }
126
127        let result = determine_phase(flow_eq, &mid, order_param_index, n_steps);
128        match result.phase {
129            FleetPhase::Ordered => lo = mid,
130            FleetPhase::Disordered => hi = mid,
131            FleetPhase::Critical => return mid,
132        }
133    }
134    (0..dim).map(|i| 0.5 * (lo[i] + hi[i])).collect()
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::flow::{GaussianFlow, Ising1DFlow};
141
142    #[test]
143    fn test_disordered_phase_1d_ising() {
144        // Small K (high T) → disordered
145        let flow_eq = Ising1DFlow::new(2.0);
146        let result = determine_phase(&flow_eq, &[0.01, 0.0], 0, 30);
147        assert_eq!(result.phase, FleetPhase::Disordered);
148    }
149
150    #[test]
151    fn test_ordered_direction_gaussian() {
152        // Large r → flow diverges → ordered
153        let flow_eq = GaussianFlow::new(3, 2.0);
154        let result = determine_phase(&flow_eq, &[100.0, 0.0], 0, 20);
155        assert_eq!(result.phase, FleetPhase::Ordered);
156    }
157
158    #[test]
159    fn test_disordered_direction_gaussian() {
160        // r=0 at Gaussian fixed point → disordered
161        let flow_eq = GaussianFlow::new(3, 2.0);
162        let result = determine_phase(&flow_eq, &[0.0, 0.0], 0, 10);
163        assert_eq!(result.phase, FleetPhase::Disordered);
164    }
165
166    #[test]
167    fn test_critical_surface_codimension() {
168        // In d=3, Gaussian fixed point: r eigenvalue b²=4 (relevant), u eigenvalue b^(4-3)=b=2 (relevant)
169        // So codimension = 2. Use d=5 where u is irrelevant → codimension = 1
170        let flow_eq = GaussianFlow::new(5, 2.0);
171        let surface = compute_critical_surface(&flow_eq, &[0.0, 0.0]);
172        assert_eq!(surface.codimension, 1); // Only r is relevant in d=5
173    }
174
175    #[test]
176    fn test_bisection_finds_critical() {
177        // For Gaussian flow, critical point is at r=0
178        let flow_eq = GaussianFlow::new(3, 2.0);
179        let critical = find_critical_point(
180            &flow_eq,
181            &[1.0, 0.0],  // ordered
182            &[-1.0, 0.0], // disordered (negative r doesn't exist, but let's test)
183            0,
184            20,
185            0.01,
186            50,
187        );
188        // The critical point should be near r=0
189        // Note: for Gaussian flow, negative r also grows, so this is a bit degenerate
190        // The bisection should still converge to something sensible
191        assert!(critical[0].abs() < 1.0);
192    }
193}