lau_diffusion_agents/
fractional.rs1use nalgebra::DVector;
4use serde::{Serialize, Deserialize};
5use rand::Rng;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct StableParams {
9 pub alpha: f64,
10 pub beta: f64,
11 pub scale: f64,
12 pub location: f64,
13}
14
15impl Default for StableParams {
16 fn default() -> Self { Self { alpha: 2.0, beta: 0.0, scale: 1.0, location: 0.0 } }
17}
18
19pub fn stable_random(params: &StableParams) -> f64 {
20 let mut rng = rand::thread_rng();
21 if (params.alpha - 2.0).abs() < 1e-10 {
22 let u: f64 = rng.gen_range(-1.0..1.0);
23 return params.location + params.scale * u * std::f64::consts::SQRT_2;
24 }
25 let u: f64 = rng.gen_range(-std::f64::consts::FRAC_PI_2..std::f64::consts::FRAC_PI_2);
26 let w: f64 = -rng.gen_range(0.01..1.0_f64).ln();
27 let alpha = params.alpha;
28 let zeta = if params.beta.abs() < 1e-10 { 0.0 }
29 else { params.beta * (std::f64::consts::FRAC_PI_2 * alpha / 2.0).tan() };
30 let term1 = (1.0 + zeta * zeta).powf(1.0 / (2.0 * alpha));
31 let term2 = (u + zeta).sin();
32 let cos_u = (u + zeta).cos().max(1e-15);
33 let term3 = (cos_u).powf((1.0 - alpha) / alpha);
34 let x = term1 * term2 * term3 * w.powf((1.0 - alpha) / alpha);
35 params.location + params.scale * x
36}
37
38pub fn stable_samples(params: &StableParams, n: usize) -> Vec<f64> {
39 (0..n).map(|_| stable_random(params)).collect()
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LevyFlight {
44 pub alpha: f64,
45 pub positions: Vec<DVector<f64>>,
46 pub step_sizes: Vec<f64>,
47}
48
49impl LevyFlight {
50 pub fn simulate(dimension: usize, alpha: f64, n_steps: usize, scale: f64) -> Self {
51 let params = StableParams { alpha, beta: 0.0, scale, location: 0.0 };
52 let mut rng = rand::thread_rng();
53 let mut positions = Vec::with_capacity(n_steps + 1);
54 let mut step_sizes = Vec::with_capacity(n_steps);
55 let mut pos = DVector::zeros(dimension);
56 positions.push(pos.clone());
57
58 for _ in 0..n_steps {
59 let step_length = stable_random(¶ms).abs();
60 step_sizes.push(step_length);
61 let mut direction = DVector::zeros(dimension);
62 for j in 0..dimension {
63 let u: f64 = rng.gen_range(-1.0..1.0);
64 direction[j] = u;
65 }
66 let norm = direction.iter().map(|x| x * x).sum::<f64>().sqrt();
67 if norm > 1e-15 {
68 direction = direction.scale(step_length / norm);
69 }
70 pos += &direction;
71 positions.push(pos.clone());
72 }
73 LevyFlight { alpha, positions, step_sizes }
74 }
75
76 pub fn mean_square_displacement(&self) -> Vec<f64> {
77 let origin = &self.positions[0];
78 self.positions.iter().map(|p| { let d = p - origin; d.iter().map(|x| x * x).sum::<f64>() }).collect()
79 }
80}
81
82pub struct FractionalDiffusion1D { pub n_points: usize, pub alpha: f64 }
83
84impl FractionalDiffusion1D {
85 pub fn new(n_points: usize, alpha: f64) -> Self {
86 assert!((0.0..=2.0).contains(&alpha), "Alpha must be in (0, 2]");
87 Self { n_points, alpha }
88 }
89
90 pub fn gl_coefficients(&self, n: usize) -> Vec<f64> {
91 let mut coeffs = vec![0.0; n + 1];
92 coeffs[0] = 1.0;
93 for k in 1..=n {
94 coeffs[k] = coeffs[k - 1] * (1.0 - (self.alpha + 1.0) / k as f64);
95 }
96 coeffs
97 }
98
99 pub fn solve(&self, u0: &[f64], dx: f64, dt: f64, n_steps: usize) -> Vec<f64> {
100 let n = self.n_points;
101 let mut u = u0.to_vec();
102 let coeffs = self.gl_coefficients(n);
103 let r = dt / dx.powf(self.alpha);
104
105 for _ in 0..n_steps {
106 let mut u_new = u.clone();
107 for i in 1..n - 1 {
108 let mut sum = 0.0;
109 for k in 0..=i { sum += coeffs[k] * u[i - k]; }
110 u_new[i] = u[i] - r * sum;
111 u_new[i] = u_new[i].max(0.0);
112 }
113 u_new[0] = 0.0;
114 u_new[n - 1] = 0.0;
115 u = u_new;
116 }
117 u
118 }
119}
120
121pub fn stable_characteristic_function(t: f64, params: &StableParams) -> num_complex::Complex64 {
122 use num_complex::Complex64;
123 let exp_part = (-params.scale * t.abs()).exp();
124 Complex64::new(exp_part, 0.0) * Complex64::new(0.0, params.location * t).exp()
125}
126
127pub fn anomalous_exponent(msd: &[f64], dt: f64) -> f64 {
128 if msd.len() < 3 { return 1.0; }
129 let n = msd.len();
130 let log_t: Vec<f64> = (0..n).map(|i| ((i as f64 * dt).max(1e-10)).ln()).collect();
131 let log_msd: Vec<f64> = msd.iter().map(|&m| m.max(1e-10).ln()).collect();
132 let n_f = n as f64;
133 let sum_x: f64 = log_t.iter().sum();
134 let sum_y: f64 = log_msd.iter().sum();
135 let sum_xy: f64 = log_t.iter().zip(log_msd.iter()).map(|(x, y)| x * y).sum();
136 let sum_xx: f64 = log_t.iter().map(|x| x * x).sum();
137 (n_f * sum_xy - sum_x * sum_y) / (n_f * sum_xx - sum_x * sum_x)
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use approx::assert_relative_eq;
144
145 #[test]
146 fn test_stable_params_default() {
147 let p = StableParams::default();
148 assert_eq!(p.alpha, 2.0);
149 assert_eq!(p.beta, 0.0);
150 }
151
152 #[test]
153 fn test_stable_gaussian() {
154 let params = StableParams { alpha: 2.0, ..Default::default() };
155 let samples = stable_samples(¶ms, 1000);
156 assert_eq!(samples.len(), 1000);
157 let mean = samples.iter().sum::<f64>() / 1000.0;
158 assert!(mean.abs() < 1.0, "Mean too far from 0: {}", mean);
159 }
160
161 #[test]
162 fn test_stable_heavy_tail() {
163 let params = StableParams { alpha: 1.5, beta: 0.0, scale: 1.0, location: 0.0 };
164 let samples = stable_samples(¶ms, 1000);
165 let max_abs = samples.iter().map(|x| x.abs()).fold(0.0_f64, f64::max);
166 assert!(max_abs > 1.0, "Expected heavy tails, max_abs={}", max_abs);
167 }
168
169 #[test]
170 fn test_levy_flight() {
171 let flight = LevyFlight::simulate(2, 1.5, 100, 1.0);
172 assert_eq!(flight.positions.len(), 101);
173 assert_eq!(flight.step_sizes.len(), 100);
174 }
175
176 #[test]
177 fn test_levy_flight_msd() {
178 let flight = LevyFlight::simulate(1, 1.5, 200, 1.0);
179 let msd = flight.mean_square_displacement();
180 assert_eq!(msd.len(), 201);
181 assert!(msd[100] > msd[10]);
182 }
183
184 #[test]
185 fn test_gl_coefficients() {
186 let fd = FractionalDiffusion1D::new(50, 1.5);
187 let coeffs = fd.gl_coefficients(5);
188 assert_relative_eq!(coeffs[0], 1.0);
189 assert!(coeffs[1] < 0.0);
190 }
191
192 #[test]
193 fn test_fractional_diffusion_solve() {
194 let fd = FractionalDiffusion1D::new(50, 1.5);
195 let u0: Vec<f64> = (0..50).map(|i| {
196 let x = i as f64 / 49.0;
197 (-((x - 0.5) * 10.0).powi(2)).exp()
198 }).collect();
199 let result = fd.solve(&u0, 0.02, 0.0001, 10);
200 assert_eq!(result.len(), 50);
201 assert!(result.iter().all(|&v| v >= 0.0));
202 }
203
204 #[test]
205 fn test_characteristic_function_magnitude() {
206 let params = StableParams { alpha: 2.0, ..Default::default() };
207 let phi = stable_characteristic_function(0.0, ¶ms);
208 assert_relative_eq!(phi.norm(), 1.0, epsilon = 1e-10);
209 }
210
211 #[test]
212 fn test_anomalous_exponent_range() {
213 let msd: Vec<f64> = (1..=100).map(|i| (i as f64).powf(1.5)).collect();
214 let gamma = anomalous_exponent(&msd, 1.0);
215 assert!(gamma > 0.0, "gamma={}", gamma);
216 }
217
218 #[test]
219 fn test_fractional_diffusion_alpha_bounds() {
220 let fd = FractionalDiffusion1D::new(10, 1.0);
221 assert_eq!(fd.alpha, 1.0);
222 }
223}