1use crate::linalg;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct RGFlow {
12 pub parameters: Vec<f64>,
14 pub scale: usize,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct FlowStep {
21 pub scale: usize,
23 pub parameters: Vec<f64>,
25 pub delta: Vec<f64>,
27}
28
29pub trait FlowEquation: Send + Sync {
32 fn flow_step(&self, params: &[f64], scale: usize) -> Vec<f64>;
34
35 fn dim(&self) -> usize;
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct Ising1DFlow {
46 pub b: f64,
48}
49
50impl Ising1DFlow {
51 pub fn new(b: f64) -> Self {
52 Self { b }
53 }
54}
55
56impl FlowEquation for Ising1DFlow {
57 fn flow_step(&self, params: &[f64], _scale: usize) -> Vec<f64> {
58 let k = params[0];
59 let tanh_k = k.tanh();
61 let new_k = tanh_k * tanh_k;
62 let k_prime = new_k.atanh();
63 let h = if params.len() > 1 { params[1] } else { 0.0 };
65 let h_prime = if h.abs() < 1e-15 {
66 0.0
67 } else {
68 2.0 * h };
71 vec![k_prime, h_prime]
72 }
73
74 fn dim(&self) -> usize {
75 2
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct GaussianFlow {
85 pub d: usize,
87 pub b: f64,
89}
90
91impl GaussianFlow {
92 pub fn new(d: usize, b: f64) -> Self {
93 Self { d, b }
94 }
95}
96
97impl FlowEquation for GaussianFlow {
98 fn flow_step(&self, params: &[f64], _scale: usize) -> Vec<f64> {
99 let r = params[0];
100 let u = if params.len() > 1 { params[1] } else { 0.0 };
101 let r_prime = self.b * self.b * r;
102 let u_prime = self.b.powi(4 - self.d as i32) * u;
103 vec![r_prime, u_prime]
104 }
105
106 fn dim(&self) -> usize {
107 2
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct WilsonFisherFlow {
117 pub d: f64,
119 pub n: usize,
121 pub dl: f64,
123}
124
125impl WilsonFisherFlow {
126 pub fn new(d: f64, n: usize, dl: f64) -> Self {
127 Self { d, n, dl }
128 }
129}
130
131impl FlowEquation for WilsonFisherFlow {
132 fn flow_step(&self, params: &[f64], _scale: usize) -> Vec<f64> {
133 let r = params[0];
134 let u = params[1];
135 let epsilon = 4.0 - self.d;
136 let pi2 = std::f64::consts::PI * std::f64::consts::PI;
137
138 let dr = 2.0 * r + (self.n as f64 + 2.0) * u / (6.0 * pi2);
139 let du = epsilon * u - (self.n as f64 + 8.0) * u * u / (6.0 * pi2);
140
141 vec![r + self.dl * dr, u + self.dl * du]
142 }
143
144 fn dim(&self) -> usize {
145 2
146 }
147}
148
149impl RGFlow {
150 pub fn new(parameters: Vec<f64>) -> Self {
152 Self {
153 parameters,
154 scale: 0,
155 }
156 }
157
158 pub fn evolve(&mut self, flow_eq: &dyn FlowEquation, n_steps: usize) -> Vec<FlowStep> {
160 let mut trajectory = Vec::with_capacity(n_steps);
161 for _ in 0..n_steps {
162 let old_params = self.parameters.clone();
163 let new_params = flow_eq.flow_step(&self.parameters, self.scale);
164 let delta: Vec<f64> = new_params
165 .iter()
166 .zip(old_params.iter())
167 .map(|(n, o)| n - o)
168 .collect();
169 self.scale += 1;
170 self.parameters = new_params;
171 trajectory.push(FlowStep {
172 scale: self.scale,
173 parameters: self.parameters.clone(),
174 delta,
175 });
176 }
177 trajectory
178 }
179
180 pub fn stability_matrix(
183 &self,
184 flow_eq: &dyn FlowEquation,
185 ) -> Vec<Vec<f64>> {
186 linalg::jacobian(
187 &|p| flow_eq.flow_step(p, self.scale),
188 &self.parameters,
189 1e-7,
190 )
191 }
192}
193
194pub fn flow_length(trajectory: &[FlowStep]) -> f64 {
196 trajectory
197 .iter()
198 .map(|step| step.delta.iter().map(|d| d * d).sum::<f64>().sqrt())
199 .sum()
200}
201
202pub fn has_converged(trajectory: &[FlowStep], tolerance: f64, n_check: usize) -> bool {
204 let n = trajectory.len();
205 if n < n_check {
206 return false;
207 }
208 trajectory[(n - n_check)..]
209 .iter()
210 .all(|step| step.delta.iter().map(|d| d * d).sum::<f64>().sqrt() < tolerance)
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_ising_1d_flow_toward_zero() {
219 let flow_eq = Ising1DFlow::new(2.0);
222 let mut flow = RGFlow::new(vec![0.5, 0.0]);
223 let _traj = flow.evolve(&flow_eq, 20);
224 assert!(flow.parameters[0] < 0.5);
226 assert!(flow.parameters[0].abs() < 0.1);
228 }
229
230 #[test]
231 fn test_ising_1d_zero_k_fixed_point() {
232 let flow_eq = Ising1DFlow::new(2.0);
234 let result = flow_eq.flow_step(&[0.0, 0.0], 0);
235 assert!(result[0].abs() < 1e-10);
236 }
237
238 #[test]
239 fn test_gaussian_r_grows_relevant() {
240 let flow_eq = GaussianFlow::new(3, 2.0); let mut flow = RGFlow::new(vec![0.1, 0.0]);
243 let _traj = flow.evolve(&flow_eq, 5);
244 let expected = 0.1 * 4.0_f64.powi(5);
246 assert!((flow.parameters[0] - expected).abs() < 1e-10);
247 }
248
249 #[test]
250 fn test_gaussian_u_irrelevant_d3() {
251 let flow_eq = GaussianFlow::new(5, 2.0);
255 let mut flow = RGFlow::new(vec![0.0, 1.0]);
256 let _traj = flow.evolve(&flow_eq, 3);
257 let expected = 1.0 * 0.5_f64.powi(3);
259 assert!((flow.parameters[1] - expected).abs() < 1e-10);
260 }
261
262 #[test]
263 fn test_gaussian_zero_is_fixed_point() {
264 let flow_eq = GaussianFlow::new(3, 2.0);
265 let result = flow_eq.flow_step(&[0.0, 0.0], 0);
266 assert!(result[0].abs() < 1e-15);
267 assert!(result[1].abs() < 1e-15);
268 }
269
270 #[test]
271 fn test_wilson_fisher_fixed_point() {
272 let flow_eq = WilsonFisherFlow::new(3.99, 1, 0.01); let epsilon = 0.01;
275 let n = 1.0;
276 let pi2 = std::f64::consts::PI * std::f64::consts::PI;
277 let u_star = 6.0 * pi2 * epsilon / (n + 8.0);
278
279 let mut flow = RGFlow::new(vec![0.0, u_star]);
281 let _traj = flow.evolve(&flow_eq, 500);
282 assert!(flow.parameters[1] > 0.0);
284 assert!(flow.parameters[1] < u_star * 10.0);
285 }
286
287 #[test]
288 fn test_flow_convergence_detection() {
289 let flow_eq = GaussianFlow::new(3, 2.0);
290 let mut flow = RGFlow::new(vec![0.0, 0.0]); let traj = flow.evolve(&flow_eq, 5);
292 assert!(has_converged(&traj, 1e-10, 3));
293 }
294
295 #[test]
296 fn test_flow_length_positive() {
297 let flow_eq = GaussianFlow::new(3, 2.0);
298 let mut flow = RGFlow::new(vec![1.0, 0.0]);
299 let traj = flow.evolve(&flow_eq, 3);
300 assert!(flow_length(&traj) > 0.0);
301 }
302}