1use super::functions::*;
6use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
7
8pub struct LinearODE {
10 pub a: Box<dyn Fn(f64) -> f64>,
12 pub b: Box<dyn Fn(f64) -> f64>,
14 pub t0: f64,
16 pub y0: f64,
18}
19impl LinearODE {
20 pub fn new(
22 a: impl Fn(f64) -> f64 + 'static,
23 b: impl Fn(f64) -> f64 + 'static,
24 t0: f64,
25 y0: f64,
26 ) -> Self {
27 Self {
28 a: Box::new(a),
29 b: Box::new(b),
30 t0,
31 y0,
32 }
33 }
34 pub fn is_autonomous(&self) -> bool {
36 [(self.b)(0.0), (self.b)(1.0), (self.b)(-1.0)]
37 .iter()
38 .all(|v| v.abs() < 1e-14)
39 }
40}
41pub struct MultistepMethod {
43 pub h: f64,
45 pub k: usize,
47}
48impl MultistepMethod {
49 pub fn new(h: f64, k: usize) -> Self {
51 Self { h, k: k.max(2) }
52 }
53 pub fn solve_to(
57 &self,
58 f: &dyn Fn(f64, f64) -> f64,
59 t0: f64,
60 y0: f64,
61 t_end: f64,
62 ) -> Vec<(f64, f64)> {
63 let mut result = Vec::new();
64 let h = self.h;
65 let mut t = t0;
66 let mut y = y0;
67 result.push((t, y));
68 let rk4 = RungeKutta4::new(h);
69 let y1 = rk4.step(f, t, y);
70 let t1 = t + h;
71 result.push((t1, y1));
72 let mut f_prev = f(t, y);
73 let mut f_curr = f(t1, y1);
74 t = t1;
75 y = y1;
76 while t < t_end - 1e-12 {
77 let y_pred = y + h * (3.0 / 2.0 * f_curr - 1.0 / 2.0 * f_prev);
78 let t_new = t + h;
79 let f_pred = f(t_new, y_pred);
80 let y_new = y + h * (1.0 / 2.0 * f_pred + 1.0 / 2.0 * f_curr);
81 f_prev = f_curr;
82 f_curr = f(t_new, y_new);
83 t = t_new;
84 y = y_new;
85 result.push((t, y));
86 }
87 result
88 }
89}
90pub struct VectorEuler {
92 pub h: f64,
94 pub n_steps: usize,
96}
97impl VectorEuler {
98 pub fn new(h: f64, n_steps: usize) -> Self {
100 Self { h, n_steps }
101 }
102 pub fn step(&self, y: &[f64], t: f64, f: impl Fn(f64, &[f64]) -> Vec<f64>) -> Vec<f64> {
104 let fy = f(t, y);
105 y.iter()
106 .zip(fy.iter())
107 .map(|(yi, fi)| yi + self.h * fi)
108 .collect()
109 }
110 pub fn global_error_o_h(&self) -> f64 {
112 self.h
113 }
114}
115pub struct AdaptiveStepRK45 {
117 pub atol: f64,
119 pub rtol: f64,
121}
122impl AdaptiveStepRK45 {
123 pub fn new(atol: f64, rtol: f64) -> Self {
125 Self { atol, rtol }
126 }
127 pub fn adaptive_step(
129 &self,
130 f: &dyn Fn(f64, f64) -> f64,
131 t: f64,
132 y: f64,
133 h: f64,
134 ) -> (f64, f64, bool) {
135 let k1 = f(t, y);
136 let k2 = f(t + h / 5.0, y + h / 5.0 * k1);
137 let k3 = f(
138 t + 3.0 * h / 10.0,
139 y + h * (3.0 / 40.0 * k1 + 9.0 / 40.0 * k2),
140 );
141 let k4 = f(
142 t + 4.0 * h / 5.0,
143 y + h * (44.0 / 45.0 * k1 - 56.0 / 15.0 * k2 + 32.0 / 9.0 * k3),
144 );
145 let k5 = f(
146 t + 8.0 * h / 9.0,
147 y + h
148 * (19372.0 / 6561.0 * k1 - 25360.0 / 2187.0 * k2 + 64448.0 / 6561.0 * k3
149 - 212.0 / 729.0 * k4),
150 );
151 let k6 = f(
152 t + h,
153 y + h
154 * (9017.0 / 3168.0 * k1 - 355.0 / 33.0 * k2
155 + 46732.0 / 5247.0 * k3
156 + 49.0 / 176.0 * k4
157 - 5103.0 / 18656.0 * k5),
158 );
159 let y5 = y + h
160 * (35.0 / 384.0 * k1 + 500.0 / 1113.0 * k3 + 125.0 / 192.0 * k4 - 2187.0 / 6784.0 * k5
161 + 11.0 / 84.0 * k6);
162 let k7 = f(t + h, y5);
163 let y4 = y + h
164 * (5179.0 / 57600.0 * k1 + 7571.0 / 16695.0 * k3 + 393.0 / 640.0 * k4
165 - 92097.0 / 339200.0 * k5
166 + 187.0 / 2100.0 * k6
167 + 1.0 / 40.0 * k7);
168 let err = (y5 - y4).abs();
169 let tol = self.atol + self.rtol * y.abs().max(y5.abs());
170 let accepted = err <= tol;
171 let factor = if err < 1e-15 {
172 5.0
173 } else {
174 0.9 * (tol / err).powf(0.2)
175 };
176 let h_new = (h * factor).min(10.0 * h).max(h / 10.0);
177 (y5, h_new, accepted)
178 }
179 pub fn solve_to(
181 &self,
182 f: &dyn Fn(f64, f64) -> f64,
183 t0: f64,
184 y0: f64,
185 t_end: f64,
186 ) -> Vec<(f64, f64)> {
187 let mut result = Vec::new();
188 let mut t = t0;
189 let mut y = y0;
190 let mut h = (t_end - t0) / 100.0;
191 result.push((t, y));
192 while t < t_end - 1e-12 {
193 if t + h > t_end {
194 h = t_end - t;
195 }
196 let (y_new, h_new, accepted) = self.adaptive_step(f, t, y, h);
197 if accepted {
198 t += h;
199 y = y_new;
200 result.push((t, y));
201 }
202 h = h_new;
203 }
204 result
205 }
206}
207pub struct LyapunovExponent {
209 pub t_end: f64,
211 pub h: f64,
213}
214impl LyapunovExponent {
215 pub fn new(t_end: f64, h: f64) -> Self {
217 Self { t_end, h }
218 }
219 pub fn estimate(&self, f: &dyn Fn(f64, f64) -> f64, x0: f64) -> f64 {
223 let delta0 = 1e-8;
224 let rk4 = RungeKutta4::new(self.h);
225 let traj1 = rk4.solve_to(f, 0.0, x0, self.t_end);
226 let traj2 = rk4.solve_to(f, 0.0, x0 + delta0, self.t_end);
227 let y1 = traj1.last().map(|&(_, y)| y).unwrap_or(x0);
228 let y2 = traj2.last().map(|&(_, y)| y).unwrap_or(x0 + delta0);
229 let delta_t = (y2 - y1).abs();
230 if delta_t < 1e-15 || self.t_end < 1e-12 {
231 0.0
232 } else {
233 (delta_t / delta0).ln() / self.t_end
234 }
235 }
236}
237pub struct VectorRK4 {
239 pub h: f64,
241}
242impl VectorRK4 {
243 pub fn new(h: f64) -> Self {
245 Self { h }
246 }
247 pub fn step(&self, y: &[f64], t: f64, f: impl Fn(f64, &[f64]) -> Vec<f64>) -> Vec<f64> {
249 let h = self.h;
250 let k1 = f(t, y);
251 let y2: Vec<f64> = y
252 .iter()
253 .zip(&k1)
254 .map(|(yi, ki)| yi + h / 2.0 * ki)
255 .collect();
256 let k2 = f(t + h / 2.0, &y2);
257 let y3: Vec<f64> = y
258 .iter()
259 .zip(&k2)
260 .map(|(yi, ki)| yi + h / 2.0 * ki)
261 .collect();
262 let k3 = f(t + h / 2.0, &y3);
263 let y4: Vec<f64> = y.iter().zip(&k3).map(|(yi, ki)| yi + h * ki).collect();
264 let k4 = f(t + h, &y4);
265 y.iter()
266 .enumerate()
267 .map(|(i, yi)| yi + h / 6.0 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]))
268 .collect()
269 }
270 pub fn is_4th_order(&self) -> bool {
272 true
273 }
274}
275pub struct LorenzSystem {
277 pub sigma: f64,
279 pub rho: f64,
281 pub beta: f64,
283}
284impl LorenzSystem {
285 pub fn new(sigma: f64, rho: f64, beta: f64) -> Self {
287 Self { sigma, rho, beta }
288 }
289 pub fn step(&self, state: &[f64; 3], h: f64) -> [f64; 3] {
291 let lorenz = |s: &[f64; 3]| -> [f64; 3] {
292 [
293 self.sigma * (s[1] - s[0]),
294 s[0] * (self.rho - s[2]) - s[1],
295 s[0] * s[1] - self.beta * s[2],
296 ]
297 };
298 let k1 = lorenz(state);
299 let s2 = [
300 state[0] + h / 2.0 * k1[0],
301 state[1] + h / 2.0 * k1[1],
302 state[2] + h / 2.0 * k1[2],
303 ];
304 let k2 = lorenz(&s2);
305 let s3 = [
306 state[0] + h / 2.0 * k2[0],
307 state[1] + h / 2.0 * k2[1],
308 state[2] + h / 2.0 * k2[2],
309 ];
310 let k3 = lorenz(&s3);
311 let s4 = [
312 state[0] + h * k3[0],
313 state[1] + h * k3[1],
314 state[2] + h * k3[2],
315 ];
316 let k4 = lorenz(&s4);
317 [
318 state[0] + h / 6.0 * (k1[0] + 2.0 * k2[0] + 2.0 * k3[0] + k4[0]),
319 state[1] + h / 6.0 * (k1[1] + 2.0 * k2[1] + 2.0 * k3[1] + k4[1]),
320 state[2] + h / 6.0 * (k1[2] + 2.0 * k2[2] + 2.0 * k3[2] + k4[2]),
321 ]
322 }
323 pub fn is_chaotic(&self) -> bool {
325 self.rho > 24.0 && (self.sigma - 10.0).abs() < 5.0 && self.beta > 0.0
326 }
327 pub fn strange_attractor(&self) -> String {
329 if self.is_chaotic() {
330 format!(
331 "Lorenz strange attractor: fractal dimension ≈ 2.06, Hausdorff dimension ≈ 2.06, σ={}, ρ={}, β={}",
332 self.sigma, self.rho, self.beta
333 )
334 } else {
335 format!(
336 "No strange attractor: parameters σ={}, ρ={}, β={} are not in the chaotic regime.",
337 self.sigma, self.rho, self.beta
338 )
339 }
340 }
341}
342pub struct JacobianMatrix {
344 pub entries: [[f64; 2]; 2],
346}
347impl JacobianMatrix {
348 pub fn new(entries: [[f64; 2]; 2]) -> Self {
350 Self { entries }
351 }
352 pub fn det(&self) -> f64 {
354 self.entries[0][0] * self.entries[1][1] - self.entries[0][1] * self.entries[1][0]
355 }
356 pub fn trace(&self) -> f64 {
358 self.entries[0][0] + self.entries[1][1]
359 }
360 pub fn classify_equilibrium(&self) -> StabilityType {
362 let ep = EquilibriumPoint {
363 x: 0.0,
364 y: 0.0,
365 jacobian: self.entries,
366 };
367 ep.classify()
368 }
369}
370pub struct BifurcationPoint {
372 pub param_value: f64,
374 pub bif_type: String,
376}
377impl BifurcationPoint {
378 pub fn new(param_value: f64, bif_type: impl Into<String>) -> Self {
380 Self {
381 param_value,
382 bif_type: bif_type.into(),
383 }
384 }
385 pub fn saddle_node(&self) -> String {
387 format!(
388 "Saddle-node bifurcation at μ = {}: two equilibria collide and annihilate.",
389 self.param_value
390 )
391 }
392 pub fn hopf(&self) -> String {
394 format!(
395 "Hopf bifurcation at μ = {}: equilibrium loses stability, a limit cycle is born.",
396 self.param_value
397 )
398 }
399 pub fn pitchfork(&self) -> String {
401 format!(
402 "Pitchfork bifurcation at μ = {}: one equilibrium splits into three (supercritical) or vice versa.",
403 self.param_value
404 )
405 }
406}
407pub struct HeatEquation {
409 pub kappa: f64,
411}
412impl HeatEquation {
413 pub fn new(kappa: f64) -> Self {
415 Self { kappa }
416 }
417 pub fn classify_pde(&self) -> PDEType {
419 PDEType::Parabolic
420 }
421 pub fn fundamental_solution(&self, x: f64, t: f64) -> f64 {
425 if t <= 0.0 {
426 return f64::NAN;
427 }
428 let denom = (4.0 * std::f64::consts::PI * self.kappa * t).sqrt();
429 (-x * x / (4.0 * self.kappa * t)).exp() / denom
430 }
431}
432pub struct DelayDE {
434 pub tau: f64,
436 pub h: f64,
438}
439impl DelayDE {
440 pub fn new(tau: f64, h: f64) -> Self {
442 Self { tau, h }
443 }
444 pub fn solve_to(
448 &self,
449 f: &dyn Fn(f64, f64, f64) -> f64,
450 phi: &dyn Fn(f64) -> f64,
451 t0: f64,
452 y0: f64,
453 t_end: f64,
454 ) -> Vec<(f64, f64)> {
455 let mut result = Vec::new();
456 let mut t = t0;
457 let mut y = y0;
458 result.push((t, y));
459 while t < t_end - 1e-12 {
460 let h = self.h.min(t_end - t);
461 let t_del = t - self.tau;
462 let y_del = if t_del < t0 {
463 phi(t_del)
464 } else {
465 let idx = result
466 .partition_point(|&(ti, _)| ti <= t_del)
467 .saturating_sub(1);
468 result[idx].1
469 };
470 y = y + h * f(t, y, y_del);
471 t += h;
472 result.push((t, y));
473 }
474 result
475 }
476}
477impl DelayDE {
478 pub fn characteristic_equation(&self) -> String {
480 format!(
481 "Characteristic equation: λ = a + b·e^(-λτ), τ = {} (transcendental)",
482 self.tau
483 )
484 }
485 pub fn stability_region(&self) -> String {
488 format!(
489 "Stability region for y'=ay+by(t-{}): requires a + |b| < 0 (exact boundary determined by characteristic roots on imaginary axis)",
490 self.tau
491 )
492 }
493}
494pub struct StrangeAttractor {
496 pub name: &'static str,
498 pub fractal_dimension: f64,
500 pub max_lyapunov: f64,
502}
503impl StrangeAttractor {
504 pub fn new(name: &'static str, fractal_dimension: f64, max_lyapunov: f64) -> Self {
506 Self {
507 name,
508 fractal_dimension,
509 max_lyapunov,
510 }
511 }
512 pub fn is_chaotic(&self) -> bool {
514 self.max_lyapunov > 0.0
515 }
516}
517pub struct FlowMap {
519 rk4: RungeKutta4,
520}
521impl FlowMap {
522 pub fn new(h: f64) -> Self {
524 Self {
525 rk4: RungeKutta4::new(h),
526 }
527 }
528 pub fn apply(&self, f: &dyn Fn(f64, f64) -> f64, x0: f64, t: f64) -> f64 {
530 let traj = self.rk4.solve_to(f, 0.0, x0, t);
531 traj.last().map(|&(_, y)| y).unwrap_or(x0)
532 }
533}
534#[allow(dead_code)]
536pub struct ItoSDE {
537 pub name: String,
539 pub drift_a: f64,
541 pub drift_b: f64,
542 pub diffusion_sigma: f64,
544}
545#[allow(dead_code)]
546impl ItoSDE {
547 pub fn new(name: &str, drift_a: f64, drift_b: f64, diffusion_sigma: f64) -> Self {
549 ItoSDE {
550 name: name.to_string(),
551 drift_a,
552 drift_b,
553 diffusion_sigma,
554 }
555 }
556 pub fn geometric_brownian_motion(mu: f64, sigma: f64) -> Self {
559 ItoSDE::new("GBM", mu, 0.0, sigma)
560 }
561 pub fn ornstein_uhlenbeck(theta: f64, mu: f64, sigma: f64) -> Self {
563 ItoSDE::new("OU", -theta, theta * mu, sigma)
564 }
565 pub fn euler_maruyama_step(&self, x: f64, _t: f64, dt: f64, dw: f64) -> f64 {
568 let drift = self.drift_a * x + self.drift_b;
569 let diffusion = self.diffusion_sigma;
570 x + drift * dt + diffusion * dw
571 }
572 pub fn milstein_step(&self, x: f64, t: f64, dt: f64, dw: f64) -> f64 {
575 self.euler_maruyama_step(x, t, dt, dw)
576 }
577 pub fn ou_stationary_variance(&self) -> f64 {
579 let theta = -self.drift_a;
580 if theta <= 0.0 {
581 return f64::INFINITY;
582 }
583 self.diffusion_sigma * self.diffusion_sigma / (2.0 * theta)
584 }
585 pub fn ou_stationary_mean(&self) -> f64 {
587 if self.drift_a >= 0.0 {
588 return f64::NAN;
589 }
590 self.drift_b / (-self.drift_a)
591 }
592 pub fn ito_generator(&self, x: f64) -> f64 {
595 self.drift_a * x + self.drift_b
596 }
597}
598#[allow(dead_code)]
601pub struct FredholmEquation {
602 pub a: f64,
604 pub b: f64,
605 pub lambda: f64,
607 pub n_points: usize,
609}
610#[allow(dead_code)]
611impl FredholmEquation {
612 pub fn new(a: f64, b: f64, lambda: f64, n_points: usize) -> Self {
614 FredholmEquation {
615 a,
616 b,
617 lambda,
618 n_points,
619 }
620 }
621 pub fn quadrature_nodes(&self) -> Vec<f64> {
623 let h = (self.b - self.a) / self.n_points as f64;
624 (0..self.n_points)
625 .map(|i| self.a + (i as f64 + 0.5) * h)
626 .collect()
627 }
628 pub fn quadrature_weight(&self) -> f64 {
630 (self.b - self.a) / self.n_points as f64
631 }
632 pub fn neumann_series_order2(&self, f_at_nodes: &[f64], kernel_norm: f64) -> Vec<f64> {
635 let w = self.quadrature_weight();
636 let n = self.n_points;
637 let sum_f: f64 = f_at_nodes.iter().sum::<f64>();
638 let kf_approx = kernel_norm * sum_f * w;
639 (0..n)
640 .map(|i| f_at_nodes[i] + self.lambda * kf_approx)
641 .collect()
642 }
643 pub fn neumann_convergence_condition(&self, kernel_l2_norm: f64) -> bool {
645 self.lambda.abs() * kernel_l2_norm < 1.0
646 }
647}
648pub struct LaplacianEqn {
650 pub domain: [f64; 4],
652}
653impl LaplacianEqn {
654 pub fn new(x_min: f64, x_max: f64, y_min: f64, y_max: f64) -> Self {
656 Self {
657 domain: [x_min, x_max, y_min, y_max],
658 }
659 }
660 pub fn classify_pde(&self) -> PDEType {
662 PDEType::Elliptic
663 }
664}
665pub struct ODE {
667 pub f: Box<dyn Fn(f64, f64) -> f64>,
669 pub t0: f64,
671 pub y0: f64,
673}
674impl ODE {
675 pub fn new(f: impl Fn(f64, f64) -> f64 + 'static, t0: f64, y0: f64) -> Self {
677 Self {
678 f: Box::new(f),
679 t0,
680 y0,
681 }
682 }
683}
684pub struct EulerMethod {
686 pub h: f64,
688}
689impl EulerMethod {
690 pub fn new(h: f64) -> Self {
692 Self { h }
693 }
694 pub fn step(&self, f: &dyn Fn(f64, f64) -> f64, t: f64, y: f64) -> f64 {
696 y + self.h * f(t, y)
697 }
698 pub fn solve_to(
700 &self,
701 f: &dyn Fn(f64, f64) -> f64,
702 t0: f64,
703 y0: f64,
704 t_end: f64,
705 ) -> Vec<(f64, f64)> {
706 let mut result = Vec::new();
707 let mut t = t0;
708 let mut y = y0;
709 result.push((t, y));
710 while t < t_end - 1e-12 {
711 let h = self.h.min(t_end - t);
712 y = y + h * f(t, y);
713 t += h;
714 result.push((t, y));
715 }
716 result
717 }
718 pub fn global_error_o_h(&self) -> f64 {
720 self.h
721 }
722}
723pub struct StabilityAnalysis {
725 pub eigenvalues: Vec<(f64, f64)>,
727}
728impl StabilityAnalysis {
729 pub fn new(eigenvalues: Vec<(f64, f64)>) -> Self {
731 Self { eigenvalues }
732 }
733 pub fn is_stable(&self) -> bool {
735 self.eigenvalues.iter().all(|(re, _)| *re <= 1e-10)
736 }
737 pub fn is_asymptotically_stable(&self) -> bool {
739 self.eigenvalues.iter().all(|(re, _)| *re < -1e-10)
740 }
741 pub fn lyapunov_criterion(&self) -> String {
744 let max_re = self
745 .eigenvalues
746 .iter()
747 .map(|(r, _)| *r)
748 .fold(f64::NEG_INFINITY, f64::max);
749 if max_re < -1e-10 {
750 format!("Asymptotically stable (max Re(λ) = {:.4e})", max_re)
751 } else if max_re <= 1e-10 {
752 format!("Lyapunov stable — marginal (max Re(λ) = {:.4e})", max_re)
753 } else {
754 format!("Unstable (max Re(λ) = {:.4e} > 0)", max_re)
755 }
756 }
757}
758pub struct FunctionSpaceSolution {
760 pub times: Vec<f64>,
762 pub values: Vec<f64>,
764}
765impl FunctionSpaceSolution {
766 pub fn new(times: Vec<f64>, values: Vec<f64>) -> Self {
768 Self { times, values }
769 }
770 pub fn eval(&self, t: f64) -> f64 {
772 let idx = self.times.partition_point(|&ti| ti <= t);
773 if idx == 0 {
774 return self.values[0];
775 }
776 if idx >= self.times.len() {
777 return *self
778 .values
779 .last()
780 .expect("values is non-empty: checked by idx >= len condition");
781 }
782 let t0 = self.times[idx - 1];
783 let t1 = self.times[idx];
784 let y0 = self.values[idx - 1];
785 let y1 = self.values[idx];
786 if (t1 - t0).abs() < 1e-15 {
787 y0
788 } else {
789 y0 + (y1 - y0) * (t - t0) / (t1 - t0)
790 }
791 }
792 pub fn sup_norm(&self) -> f64 {
794 self.values.iter().cloned().fold(0.0_f64, f64::max)
795 }
796}
797pub struct VolterraIntegralEquation {
799 pub g: Box<dyn Fn(f64) -> f64>,
801 pub n_quad: usize,
803}
804impl VolterraIntegralEquation {
805 pub fn new(g: impl Fn(f64) -> f64 + 'static, n_quad: usize) -> Self {
807 Self {
808 g: Box::new(g),
809 n_quad,
810 }
811 }
812 pub fn solve(&self, kernel: &dyn Fn(f64, f64) -> f64, t_end: f64, n: usize) -> Vec<(f64, f64)> {
814 let h = t_end / n as f64;
815 let mut y = vec![0.0f64; n + 1];
816 y[0] = (self.g)(0.0);
817 for i in 1..=n {
818 let ti = i as f64 * h;
819 let mut integral = 0.0;
820 for j in 0..i {
821 let sj = j as f64 * h;
822 let w = if j == 0 || j == i - 1 { 0.5 } else { 1.0 };
823 integral += w * h * kernel(ti, sj) * y[j];
824 }
825 y[i] = integral + (self.g)(ti);
826 }
827 (0..=n).map(|i| (i as f64 * h, y[i])).collect()
828 }
829}
830pub struct WaveEquation {
832 pub c: f64,
834}
835impl WaveEquation {
836 pub fn new(c: f64) -> Self {
838 Self { c }
839 }
840 pub fn classify_pde(&self) -> PDEType {
842 PDEType::Hyperbolic
843 }
844}
845#[derive(Debug, Clone, PartialEq)]
847pub enum PDEType {
848 Elliptic,
850 Parabolic,
852 Hyperbolic,
854 Mixed,
856}
857#[derive(Debug, Clone)]
859pub enum BoundaryCondition {
860 Dirichlet(f64),
862 Neumann(f64),
864 Robin(f64, f64, f64),
866 Periodic,
868}
869pub struct HamiltonianSystem {
871 pub hamiltonian: String,
873 pub dim: usize,
875}
876impl HamiltonianSystem {
877 pub fn new(hamiltonian: impl Into<String>, dim: usize) -> Self {
879 Self {
880 hamiltonian: hamiltonian.into(),
881 dim,
882 }
883 }
884 pub fn hamilton_equations(&self) -> String {
886 format!(
887 "Hamilton's equations for H = {}:\n q̇ᵢ = ∂H/∂pᵢ\n ṗᵢ = -∂H/∂qᵢ (i = 1..{})",
888 self.hamiltonian, self.dim
889 )
890 }
891 pub fn symplectic_structure(&self) -> String {
893 format!(
894 "Symplectic form ω = Σᵢ dqᵢ ∧ dpᵢ on ℝ^{} (standard)",
895 2 * self.dim
896 )
897 }
898 pub fn liouville_theorem(&self) -> String {
900 "Liouville: Hamiltonian flow is volume-preserving (div X_H = 0).".to_string()
901 }
902}
903#[derive(Debug, Clone, PartialEq)]
905pub enum Manifold {
906 StableManifold,
908 UnstableManifold,
910 CenterManifold,
912}
913pub struct ExactODE {
915 pub p: Box<dyn Fn(f64, f64) -> f64>,
917 pub q: Box<dyn Fn(f64, f64) -> f64>,
919}
920impl ExactODE {
921 pub fn new(
923 p: impl Fn(f64, f64) -> f64 + 'static,
924 q: impl Fn(f64, f64) -> f64 + 'static,
925 ) -> Self {
926 Self {
927 p: Box::new(p),
928 q: Box::new(q),
929 }
930 }
931 pub fn is_exact(&self) -> bool {
933 let h = 1e-5;
934 let x0 = 1.0_f64;
935 let y0 = 1.0_f64;
936 let dp_dy = ((self.p)(x0, y0 + h) - (self.p)(x0, y0 - h)) / (2.0 * h);
937 let dq_dx = ((self.q)(x0 + h, y0) - (self.q)(x0 - h, y0)) / (2.0 * h);
938 (dp_dy - dq_dx).abs() < 1e-6
939 }
940}
941#[allow(dead_code)]
943pub struct BifurcationDiagram {
944 pub params: Vec<f64>,
946 pub fixed_points: Vec<Vec<f64>>,
948}
949#[allow(dead_code)]
950impl BifurcationDiagram {
951 pub fn pitchfork_normal_form(lambda_range: &[f64]) -> Self {
953 let mut fps = vec![];
954 for &lam in lambda_range {
955 if lam < 0.0 {
956 fps.push(vec![0.0]);
957 } else {
958 let sqrt_lam = lam.sqrt();
959 fps.push(vec![-sqrt_lam, 0.0, sqrt_lam]);
960 }
961 }
962 BifurcationDiagram {
963 params: lambda_range.to_vec(),
964 fixed_points: fps,
965 }
966 }
967 pub fn saddle_node_normal_form(lambda_range: &[f64]) -> Self {
969 let mut fps = vec![];
970 for &lam in lambda_range {
971 if lam < 0.0 {
972 let sqrt_neg = (-lam).sqrt();
973 fps.push(vec![-sqrt_neg, sqrt_neg]);
974 } else if lam == 0.0 {
975 fps.push(vec![0.0]);
976 } else {
977 fps.push(vec![]);
978 }
979 }
980 BifurcationDiagram {
981 params: lambda_range.to_vec(),
982 fixed_points: fps,
983 }
984 }
985 pub fn hopf_limit_cycle_radius(lambda: f64) -> f64 {
988 if lambda > 0.0 {
989 lambda.sqrt()
990 } else {
991 0.0
992 }
993 }
994 pub fn count_bifurcation_points(&self) -> usize {
996 let mut count = 0;
997 for i in 1..self.fixed_points.len() {
998 if self.fixed_points[i].len() != self.fixed_points[i - 1].len() {
999 count += 1;
1000 }
1001 }
1002 count
1003 }
1004}
1005pub struct StochasticDE {
1007 pub drift: String,
1009 pub diffusion: String,
1011}
1012impl StochasticDE {
1013 pub fn new(drift: impl Into<String>, diffusion: impl Into<String>) -> Self {
1015 Self {
1016 drift: drift.into(),
1017 diffusion: diffusion.into(),
1018 }
1019 }
1020 pub fn ito_formula(&self) -> String {
1022 format!(
1023 "Itô formula for dX = ({})dt + ({})dW:\n dF = (∂F/∂t + f·F' + ½g²·F'')dt + g·F'·dW",
1024 self.drift, self.diffusion
1025 )
1026 }
1027 pub fn stratonovich_vs_ito(&self) -> String {
1029 format!(
1030 "Stratonovich SDE: dX = ({})dt + ({})∘dW\n Conversion: Itô drift = Stratonovich drift - ½g·(∂g/∂X).\n Itô is the standard in mathematical finance; Stratonovich in physics.",
1031 self.drift, self.diffusion
1032 )
1033 }
1034}
1035pub struct VectorODE {
1037 pub dimension: usize,
1039 pub is_autonomous: bool,
1041 pub rhs: Box<dyn Fn(f64, &[f64]) -> Vec<f64>>,
1043}
1044impl VectorODE {
1045 pub fn new(
1047 dimension: usize,
1048 is_autonomous: bool,
1049 rhs: impl Fn(f64, &[f64]) -> Vec<f64> + 'static,
1050 ) -> Self {
1051 Self {
1052 dimension,
1053 is_autonomous,
1054 rhs: Box::new(rhs),
1055 }
1056 }
1057 pub fn rhs_vector_field(&self) -> String {
1059 if self.is_autonomous {
1060 format!(
1061 "Autonomous vector field F: ℝ^{} → ℝ^{}",
1062 self.dimension, self.dimension
1063 )
1064 } else {
1065 format!(
1066 "Non-autonomous vector field F: ℝ × ℝ^{} → ℝ^{}",
1067 self.dimension, self.dimension
1068 )
1069 }
1070 }
1071 pub fn equilibrium_points(&self, tol: f64) -> Vec<Vec<f64>> {
1073 let mut result = Vec::new();
1074 if self.dimension == 1 {
1075 let n = 200usize;
1076 for i in 0..n {
1077 let x = -5.0 + 10.0 * i as f64 / n as f64;
1078 let f = (self.rhs)(0.0, &[x]);
1079 if f[0].abs() < tol {
1080 result.push(vec![x]);
1081 }
1082 }
1083 } else if self.dimension == 2 {
1084 let n = 40usize;
1085 for i in 0..n {
1086 for j in 0..n {
1087 let x = -5.0 + 10.0 * i as f64 / n as f64;
1088 let y = -5.0 + 10.0 * j as f64 / n as f64;
1089 let f = (self.rhs)(0.0, &[x, y]);
1090 if f[0].abs() < tol && f[1].abs() < tol {
1091 result.push(vec![x, y]);
1092 }
1093 }
1094 }
1095 }
1096 result
1097 }
1098}
1099#[derive(Debug, Clone, PartialEq)]
1101pub enum StabilityType {
1102 StableNode,
1104 UnstableNode,
1106 Saddle,
1108 StableSpiral,
1110 UnstableSpiral,
1112 Center,
1114 Degenerate,
1116}
1117pub struct RungeKutta4 {
1119 pub h: f64,
1121}
1122impl RungeKutta4 {
1123 pub fn new(h: f64) -> Self {
1125 Self { h }
1126 }
1127 pub fn step(&self, f: &dyn Fn(f64, f64) -> f64, t: f64, y: f64) -> f64 {
1129 let h = self.h;
1130 let k1 = f(t, y);
1131 let k2 = f(t + h / 2.0, y + h / 2.0 * k1);
1132 let k3 = f(t + h / 2.0, y + h / 2.0 * k2);
1133 let k4 = f(t + h, y + h * k3);
1134 y + h / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4)
1135 }
1136 pub fn solve_to(
1138 &self,
1139 f: &dyn Fn(f64, f64) -> f64,
1140 t0: f64,
1141 y0: f64,
1142 t_end: f64,
1143 ) -> Vec<(f64, f64)> {
1144 let mut result = Vec::new();
1145 let mut t = t0;
1146 let mut y = y0;
1147 result.push((t, y));
1148 while t < t_end - 1e-12 {
1149 let h = self.h.min(t_end - t);
1150 let k1 = f(t, y);
1151 let k2 = f(t + h / 2.0, y + h / 2.0 * k1);
1152 let k3 = f(t + h / 2.0, y + h / 2.0 * k2);
1153 let k4 = f(t + h, y + h * k3);
1154 y += h / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4);
1155 t += h;
1156 result.push((t, y));
1157 }
1158 result
1159 }
1160 pub fn error_estimate(&self, f: &dyn Fn(f64, f64) -> f64, t: f64, y: f64) -> f64 {
1162 let y_full = self.step(f, t, y);
1163 let half = RungeKutta4::new(self.h / 2.0);
1164 let y_half1 = half.step(f, t, y);
1165 let y_half2 = half.step(f, t + self.h / 2.0, y_half1);
1166 (y_half2 - y_full).abs() / 15.0
1167 }
1168 pub fn is_4th_order(&self) -> bool {
1170 true
1171 }
1172}
1173pub struct SturmLiouville {
1175 pub p: Box<dyn Fn(f64) -> f64>,
1177 pub q: Box<dyn Fn(f64) -> f64>,
1179 pub w: Box<dyn Fn(f64) -> f64>,
1181}
1182impl SturmLiouville {
1183 pub fn new(
1185 p: impl Fn(f64) -> f64 + 'static,
1186 q: impl Fn(f64) -> f64 + 'static,
1187 w: impl Fn(f64) -> f64 + 'static,
1188 ) -> Self {
1189 Self {
1190 p: Box::new(p),
1191 q: Box::new(q),
1192 w: Box::new(w),
1193 }
1194 }
1195 pub fn approximate_lowest_eigenvalue(&self, n: usize) -> f64 {
1198 let h = 1.0 / (n + 1) as f64;
1199 let mut y: Vec<f64> = (1..=n)
1200 .map(|k| (k as f64 * h * std::f64::consts::PI).sin())
1201 .collect();
1202 let norm: f64 = y.iter().map(|v| v * v).sum::<f64>().sqrt();
1203 for v in &mut y {
1204 *v /= norm;
1205 }
1206 let mut ay: Vec<f64> = vec![0.0; n];
1207 for i in 0..n {
1208 let xi = (i + 1) as f64 * h;
1209 let yi_prev = if i == 0 { 0.0 } else { y[i - 1] };
1210 let yi_next = if i == n - 1 { 0.0 } else { y[i + 1] };
1211 ay[i] =
1212 -(self.p)(xi) * (yi_next - 2.0 * y[i] + yi_prev) / (h * h) + (self.q)(xi) * y[i];
1213 }
1214 let numerator: f64 = y.iter().zip(ay.iter()).map(|(a, b)| a * b).sum();
1215 let denominator: f64 = y
1216 .iter()
1217 .enumerate()
1218 .map(|(i, v)| {
1219 let xi = (i + 1) as f64 * h;
1220 v * v * (self.w)(xi)
1221 })
1222 .sum();
1223 if denominator.abs() < 1e-15 {
1224 0.0
1225 } else {
1226 numerator / denominator
1227 }
1228 }
1229 pub fn is_regular(&self) -> bool {
1231 let n = 10usize;
1232 (0..=n).all(|k| {
1233 let x = k as f64 / n as f64;
1234 (self.p)(x) > 1e-12 && (self.w)(x) > 1e-12
1235 })
1236 }
1237}
1238pub struct PoincareMap {
1240 pub section_value: f64,
1242 pub h: f64,
1244 pub t_max: f64,
1246}
1247impl PoincareMap {
1248 pub fn new(section_value: f64, h: f64, t_max: f64) -> Self {
1250 Self {
1251 section_value,
1252 h,
1253 t_max,
1254 }
1255 }
1256 pub fn crossings(&self, f: &dyn Fn(f64, f64) -> f64, y0: f64) -> Vec<f64> {
1258 let rk4 = RungeKutta4::new(self.h);
1259 let traj = rk4.solve_to(f, 0.0, y0, self.t_max);
1260 let s = self.section_value;
1261 let mut crossings = Vec::new();
1262 for w in traj.windows(2) {
1263 let (_, ya) = w[0];
1264 let (_, yb) = w[1];
1265 if ya < s && yb >= s {
1266 let frac = (s - ya) / (yb - ya);
1267 crossings.push(ya + frac * (yb - ya));
1268 }
1269 }
1270 crossings
1271 }
1272}
1273pub struct EquilibriumPoint {
1275 pub x: f64,
1277 pub y: f64,
1279 pub jacobian: [[f64; 2]; 2],
1281}
1282impl EquilibriumPoint {
1283 pub fn new(f: &dyn Fn(f64, f64) -> (f64, f64), x: f64, y: f64) -> Self {
1285 let h = 1e-5;
1286 let (fp0, fp1) = f(x + h, y);
1287 let (fm0, fm1) = f(x - h, y);
1288 let (fq0, fq1) = f(x, y + h);
1289 let (fq0m, fq1m) = f(x, y - h);
1290 let df00 = (fp0 - fm0) / (2.0 * h);
1291 let df01 = (fq0 - fq0m) / (2.0 * h);
1292 let df10 = (fp1 - fm1) / (2.0 * h);
1293 let df11 = (fq1 - fq1m) / (2.0 * h);
1294 Self {
1295 x,
1296 y,
1297 jacobian: [[df00, df01], [df10, df11]],
1298 }
1299 }
1300 pub fn classify(&self) -> StabilityType {
1302 let [[a, b], [c, d]] = self.jacobian;
1303 let trace = a + d;
1304 let det = a * d - b * c;
1305 let discriminant = trace * trace - 4.0 * det;
1306 if det < -1e-10 {
1307 StabilityType::Saddle
1308 } else if discriminant < -1e-10 {
1309 if trace < -1e-10 {
1310 StabilityType::StableSpiral
1311 } else if trace > 1e-10 {
1312 StabilityType::UnstableSpiral
1313 } else {
1314 StabilityType::Center
1315 }
1316 } else if det.abs() < 1e-10 {
1317 StabilityType::Degenerate
1318 } else {
1319 if trace < -1e-10 {
1320 StabilityType::StableNode
1321 } else if trace > 1e-10 {
1322 StabilityType::UnstableNode
1323 } else {
1324 StabilityType::Degenerate
1325 }
1326 }
1327 }
1328}
1329pub struct FredholmIntegralEquation {
1331 pub lambda: f64,
1333 pub a: f64,
1335 pub b: f64,
1337 pub g: Box<dyn Fn(f64) -> f64>,
1339}
1340impl FredholmIntegralEquation {
1341 pub fn new(lambda: f64, a: f64, b: f64, g: impl Fn(f64) -> f64 + 'static) -> Self {
1343 Self {
1344 lambda,
1345 a,
1346 b,
1347 g: Box::new(g),
1348 }
1349 }
1350 pub fn solve(&self, kernel: &dyn Fn(f64, f64) -> f64, n: usize) -> Option<Vec<(f64, f64)>> {
1352 let h = (self.b - self.a) / (n - 1) as f64;
1353 let nodes: Vec<f64> = (0..n).map(|i| self.a + i as f64 * h).collect();
1354 let weights: Vec<f64> = (0..n)
1355 .map(|j| if j == 0 || j == n - 1 { h / 2.0 } else { h })
1356 .collect();
1357 let mut mat = vec![vec![0.0f64; n]; n];
1358 for i in 0..n {
1359 for j in 0..n {
1360 mat[i][j] = if i == j { 1.0 } else { 0.0 }
1361 - self.lambda * kernel(nodes[i], nodes[j]) * weights[j];
1362 }
1363 }
1364 let rhs: Vec<f64> = nodes.iter().map(|&t| (self.g)(t)).collect();
1365 let y_vals = solve_linear(mat, rhs)?;
1366 Some(nodes.into_iter().zip(y_vals).collect())
1367 }
1368}
1369pub struct LimSet {
1371 pub transient_time: f64,
1373 pub sample_interval: f64,
1375 pub num_samples: usize,
1377}
1378impl LimSet {
1379 pub fn new(transient_time: f64, sample_interval: f64, num_samples: usize) -> Self {
1381 Self {
1382 transient_time,
1383 sample_interval,
1384 num_samples,
1385 }
1386 }
1387 pub fn approximate(&self, f: &dyn Fn(f64, f64) -> f64, x0: f64) -> Vec<f64> {
1389 let h = 1e-2;
1390 let rk4 = RungeKutta4::new(h);
1391 let traj = rk4.solve_to(f, 0.0, x0, self.transient_time);
1392 let (_, mut y) = traj.last().copied().unwrap_or((0.0, x0));
1393 let mut t = self.transient_time;
1394 let mut samples = Vec::with_capacity(self.num_samples);
1395 for _ in 0..self.num_samples {
1396 let seg = rk4.solve_to(f, t, y, t + self.sample_interval);
1397 let (t_new, y_new) = seg.last().copied().unwrap_or((t, y));
1398 t = t_new;
1399 y = y_new;
1400 samples.push(y);
1401 }
1402 samples
1403 }
1404}
1405#[allow(dead_code)]
1408pub struct VolterraEquation {
1409 pub a: f64,
1411 pub lambda: f64,
1413}
1414#[allow(dead_code)]
1415impl VolterraEquation {
1416 pub fn new(a: f64, lambda: f64) -> Self {
1418 VolterraEquation { a, lambda }
1419 }
1420 pub fn picard_iterations_needed(&self, tolerance: f64) -> u64 {
1424 if self.lambda.abs() < tolerance {
1425 return 1;
1426 }
1427 let rate = self.lambda.abs();
1428 if rate >= 1.0 {
1429 return u64::MAX;
1430 }
1431 (tolerance.ln() / rate.ln()).ceil().abs() as u64
1432 }
1433}
1434pub struct StiffSolver {
1436 pub h: f64,
1438 pub tol: f64,
1440}
1441impl StiffSolver {
1442 pub fn new(h: f64, tol: f64) -> Self {
1444 Self { h, tol }
1445 }
1446 pub fn step(&self, f: &dyn Fn(f64, f64) -> f64, t: f64, y: f64) -> f64 {
1448 let h = self.h;
1449 let t_new = t + h;
1450 let mut y_new = y;
1451 for _ in 0..50 {
1452 let g = y_new - y - h * f(t_new, y_new);
1453 let eps = 1e-7;
1454 let dg = 1.0 - h * (f(t_new, y_new + eps) - f(t_new, y_new - eps)) / (2.0 * eps);
1455 let delta = g / dg;
1456 y_new -= delta;
1457 if delta.abs() < self.tol {
1458 break;
1459 }
1460 }
1461 y_new
1462 }
1463 pub fn solve_to(
1465 &self,
1466 f: &dyn Fn(f64, f64) -> f64,
1467 t0: f64,
1468 y0: f64,
1469 t_end: f64,
1470 ) -> Vec<(f64, f64)> {
1471 let mut result = Vec::new();
1472 let mut t = t0;
1473 let mut y = y0;
1474 result.push((t, y));
1475 while t < t_end - 1e-12 {
1476 let h = self.h.min(t_end - t);
1477 let t_new = t + h;
1478 let mut y_new = y;
1479 let tol = self.tol;
1480 for _ in 0..50 {
1481 let g = y_new - y - h * f(t_new, y_new);
1482 let eps = 1e-7;
1483 let dg = 1.0 - h * (f(t_new, y_new + eps) - f(t_new, y_new - eps)) / (2.0 * eps);
1484 let delta = g / dg;
1485 y_new -= delta;
1486 if delta.abs() < tol {
1487 break;
1488 }
1489 }
1490 t = t_new;
1491 y = y_new;
1492 result.push((t, y));
1493 }
1494 result
1495 }
1496}