use crate::linalg;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RGFlow {
pub parameters: Vec<f64>,
pub scale: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlowStep {
pub scale: usize,
pub parameters: Vec<f64>,
pub delta: Vec<f64>,
}
pub trait FlowEquation: Send + Sync {
fn flow_step(&self, params: &[f64], scale: usize) -> Vec<f64>;
fn dim(&self) -> usize;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ising1DFlow {
pub b: f64,
}
impl Ising1DFlow {
pub fn new(b: f64) -> Self {
Self { b }
}
}
impl FlowEquation for Ising1DFlow {
fn flow_step(&self, params: &[f64], _scale: usize) -> Vec<f64> {
let k = params[0];
let tanh_k = k.tanh();
let new_k = tanh_k * tanh_k;
let k_prime = new_k.atanh();
let h = if params.len() > 1 { params[1] } else { 0.0 };
let h_prime = if h.abs() < 1e-15 {
0.0
} else {
2.0 * h };
vec![k_prime, h_prime]
}
fn dim(&self) -> usize {
2
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GaussianFlow {
pub d: usize,
pub b: f64,
}
impl GaussianFlow {
pub fn new(d: usize, b: f64) -> Self {
Self { d, b }
}
}
impl FlowEquation for GaussianFlow {
fn flow_step(&self, params: &[f64], _scale: usize) -> Vec<f64> {
let r = params[0];
let u = if params.len() > 1 { params[1] } else { 0.0 };
let r_prime = self.b * self.b * r;
let u_prime = self.b.powi(4 - self.d as i32) * u;
vec![r_prime, u_prime]
}
fn dim(&self) -> usize {
2
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WilsonFisherFlow {
pub d: f64,
pub n: usize,
pub dl: f64,
}
impl WilsonFisherFlow {
pub fn new(d: f64, n: usize, dl: f64) -> Self {
Self { d, n, dl }
}
}
impl FlowEquation for WilsonFisherFlow {
fn flow_step(&self, params: &[f64], _scale: usize) -> Vec<f64> {
let r = params[0];
let u = params[1];
let epsilon = 4.0 - self.d;
let pi2 = std::f64::consts::PI * std::f64::consts::PI;
let dr = 2.0 * r + (self.n as f64 + 2.0) * u / (6.0 * pi2);
let du = epsilon * u - (self.n as f64 + 8.0) * u * u / (6.0 * pi2);
vec![r + self.dl * dr, u + self.dl * du]
}
fn dim(&self) -> usize {
2
}
}
impl RGFlow {
pub fn new(parameters: Vec<f64>) -> Self {
Self {
parameters,
scale: 0,
}
}
pub fn evolve(&mut self, flow_eq: &dyn FlowEquation, n_steps: usize) -> Vec<FlowStep> {
let mut trajectory = Vec::with_capacity(n_steps);
for _ in 0..n_steps {
let old_params = self.parameters.clone();
let new_params = flow_eq.flow_step(&self.parameters, self.scale);
let delta: Vec<f64> = new_params
.iter()
.zip(old_params.iter())
.map(|(n, o)| n - o)
.collect();
self.scale += 1;
self.parameters = new_params;
trajectory.push(FlowStep {
scale: self.scale,
parameters: self.parameters.clone(),
delta,
});
}
trajectory
}
pub fn stability_matrix(
&self,
flow_eq: &dyn FlowEquation,
) -> Vec<Vec<f64>> {
linalg::jacobian(
&|p| flow_eq.flow_step(p, self.scale),
&self.parameters,
1e-7,
)
}
}
pub fn flow_length(trajectory: &[FlowStep]) -> f64 {
trajectory
.iter()
.map(|step| step.delta.iter().map(|d| d * d).sum::<f64>().sqrt())
.sum()
}
pub fn has_converged(trajectory: &[FlowStep], tolerance: f64, n_check: usize) -> bool {
let n = trajectory.len();
if n < n_check {
return false;
}
trajectory[(n - n_check)..]
.iter()
.all(|step| step.delta.iter().map(|d| d * d).sum::<f64>().sqrt() < tolerance)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ising_1d_flow_toward_zero() {
let flow_eq = Ising1DFlow::new(2.0);
let mut flow = RGFlow::new(vec![0.5, 0.0]);
let _traj = flow.evolve(&flow_eq, 20);
assert!(flow.parameters[0] < 0.5);
assert!(flow.parameters[0].abs() < 0.1);
}
#[test]
fn test_ising_1d_zero_k_fixed_point() {
let flow_eq = Ising1DFlow::new(2.0);
let result = flow_eq.flow_step(&[0.0, 0.0], 0);
assert!(result[0].abs() < 1e-10);
}
#[test]
fn test_gaussian_r_grows_relevant() {
let flow_eq = GaussianFlow::new(3, 2.0); let mut flow = RGFlow::new(vec![0.1, 0.0]);
let _traj = flow.evolve(&flow_eq, 5);
let expected = 0.1 * 4.0_f64.powi(5);
assert!((flow.parameters[0] - expected).abs() < 1e-10);
}
#[test]
fn test_gaussian_u_irrelevant_d3() {
let flow_eq = GaussianFlow::new(5, 2.0);
let mut flow = RGFlow::new(vec![0.0, 1.0]);
let _traj = flow.evolve(&flow_eq, 3);
let expected = 1.0 * 0.5_f64.powi(3);
assert!((flow.parameters[1] - expected).abs() < 1e-10);
}
#[test]
fn test_gaussian_zero_is_fixed_point() {
let flow_eq = GaussianFlow::new(3, 2.0);
let result = flow_eq.flow_step(&[0.0, 0.0], 0);
assert!(result[0].abs() < 1e-15);
assert!(result[1].abs() < 1e-15);
}
#[test]
fn test_wilson_fisher_fixed_point() {
let flow_eq = WilsonFisherFlow::new(3.99, 1, 0.01); let epsilon = 0.01;
let n = 1.0;
let pi2 = std::f64::consts::PI * std::f64::consts::PI;
let u_star = 6.0 * pi2 * epsilon / (n + 8.0);
let mut flow = RGFlow::new(vec![0.0, u_star]);
let _traj = flow.evolve(&flow_eq, 500);
assert!(flow.parameters[1] > 0.0);
assert!(flow.parameters[1] < u_star * 10.0);
}
#[test]
fn test_flow_convergence_detection() {
let flow_eq = GaussianFlow::new(3, 2.0);
let mut flow = RGFlow::new(vec![0.0, 0.0]); let traj = flow.evolve(&flow_eq, 5);
assert!(has_converged(&traj, 1e-10, 3));
}
#[test]
fn test_flow_length_positive() {
let flow_eq = GaussianFlow::new(3, 2.0);
let mut flow = RGFlow::new(vec![1.0, 0.0]);
let traj = flow.evolve(&flow_eq, 3);
assert!(flow_length(&traj) > 0.0);
}
}