1use crate::solvers::{SolverConfig, SolverResult, SolverState, SolversError};
8
9pub mod analysis;
13pub mod capabilities;
14pub mod dense;
15pub mod differential;
16pub mod exterior;
17pub mod grid;
18pub mod manifold;
19pub mod mechanics;
20pub mod ode_adaptive;
21pub mod ode_advanced;
22pub mod ode_solver;
23pub mod potential;
24pub mod quadrature;
25pub mod tensor_integrity;
26pub mod tensor_provenance;
27pub mod workspace;
28
29#[repr(C)]
31pub struct RungeKutta4Static {
32 pub state: [f64; 4],
34 pub dt: f64,
36 pub t: f64,
38 pub config: SolverConfig,
40 pub solver_state: SolverState,
42}
43
44#[repr(C)]
46#[derive(Debug, Clone, Copy, PartialEq)]
47pub struct ODEState {
48 pub y: [f64; 4],
50 pub t: f64,
52 pub dt: f64,
54}
55
56#[repr(C)]
58pub struct ShootingMethodBVP {
59 pub initial_guess: [f64; 4],
61 pub target_values: [f64; 4],
63 pub trajectory: [ODEState; 100],
65 pub boundary_error: [f64; 4],
67 pub config: SolverConfig,
69 pub solver_state: SolverState,
71}
72
73#[repr(C)]
75#[derive(Debug, Clone, Copy, PartialEq)]
76pub struct BVPState {
77 pub iteration: u32,
79 pub boundary_error: f64,
81 pub converged: bool,
83 pub shooting_params: [f64; 4],
85}
86
87#[repr(C)]
89pub struct SimpsonsIntegratorChunked {
90 pub chunk_buffer: [f64; 12],
93 pub chunk_index: u16,
95 pub accumulated_integral: f64,
97 pub a: f64,
99 pub b: f64,
100 pub chunks_processed: u16,
102 pub config: SolverConfig,
104 pub solver_state: SolverState,
106}
107
108#[repr(C)]
110#[derive(Clone, Copy)]
111pub struct IntegralChunk {
112 pub values: [f64; 12],
114 pub x_coords: [f64; 12],
116 pub contribution: f64,
118}
119
120pub trait ODEFunction {
122 fn derivatives(&self, t: f64, y: &[f64; 4]) -> [f64; 4];
124}
125
126pub trait BVPFunction {
128 fn derivatives(&self, t: f64, y: &[f64; 4], params: &[f64; 4]) -> [f64; 4];
130
131 fn boundary_residuals(&self, y0: &[f64; 4], y1: &[f64; 4]) -> [f64; 4];
133}
134
135pub trait IntegrandFunction {
137 fn evaluate(&self, x: f64) -> f64;
139}
140
141impl RungeKutta4Static {
142 pub fn new(dt: f64, config: SolverConfig) -> Self {
144 Self {
145 state: [0.0; 4],
146 dt,
147 t: 0.0,
148 config,
149 solver_state: SolverState::default(),
150 }
151 }
152
153 pub fn integrate<F>(
155 &mut self,
156 f: &F,
157 t0: f64,
158 y0: [f64; 4],
159 t_final: f64,
160 ) -> SolverResult<ODEState>
161 where
162 F: ODEFunction,
163 {
164 if !self.dt.is_finite()
165 || self.dt <= 0.0
166 || !t0.is_finite()
167 || !t_final.is_finite()
168 || t_final < t0
169 || y0.iter().any(|value| !value.is_finite())
170 {
171 return Err(SolversError::InvalidParameters);
172 }
173
174 self.state = y0;
176 self.t = t0;
177 self.solver_state.iteration = 0;
178 self.solver_state.converged = false;
179
180 while self.t < t_final {
182 if self.solver_state.iteration >= self.config.max_iterations {
183 return Err(SolversError::ConvergenceFailed);
184 }
185
186 let remaining_time = t_final - self.t;
188 let dt = if remaining_time < self.dt {
189 remaining_time
190 } else {
191 self.dt
192 };
193
194 self.rk4_step(f, dt)?;
196
197 self.solver_state.iteration += 1;
199 }
200 self.solver_state.converged = true;
201
202 Ok(ODEState {
203 y: self.state,
204 t: self.t,
205 dt: self.dt,
206 })
207 }
208
209 pub fn integrate_until_steady_state<F>(
213 &mut self,
214 f: &F,
215 t0: f64,
216 y0: [f64; 4],
217 t_limit: f64,
218 ) -> SolverResult<ODEState>
219 where
220 F: ODEFunction,
221 {
222 if !self.dt.is_finite()
223 || self.dt <= 0.0
224 || !t0.is_finite()
225 || !t_limit.is_finite()
226 || t_limit < t0
227 || !self.config.tolerance.is_finite()
228 || self.config.tolerance < 0.0
229 || y0.iter().any(|value| !value.is_finite())
230 {
231 return Err(SolversError::InvalidParameters);
232 }
233
234 self.state = y0;
235 self.t = t0;
236 self.solver_state = SolverState::default();
237
238 loop {
239 let derivative = f.derivatives(self.t, &self.state);
240 if derivative.iter().any(|value| !value.is_finite()) {
241 return Err(SolversError::ComputationError);
242 }
243 let norm = derivative
244 .iter()
245 .fold(0.0_f64, |largest, value| largest.max(value.abs()));
246 self.solver_state.error = norm;
247 if norm <= self.config.tolerance {
248 self.solver_state.converged = true;
249 return Ok(self.get_state());
250 }
251 if self.t >= t_limit || self.solver_state.iteration >= self.config.max_iterations {
252 return Err(SolversError::ConvergenceFailed);
253 }
254
255 let dt = self.dt.min(t_limit - self.t);
256 self.rk4_step(f, dt)?;
257 self.solver_state.iteration += 1;
258 }
259 }
260
261 fn rk4_step<F>(&mut self, f: &F, dt: f64) -> SolverResult<()>
263 where
264 F: ODEFunction,
265 {
266 let k1 = f.derivatives(self.t, &self.state);
268 if k1.iter().any(|value| !value.is_finite()) {
269 return Err(SolversError::ComputationError);
270 }
271
272 let _y_temp: [f64; 4];
273 let mut y_temp = [0.0; 4];
274 for i in 0..4 {
275 y_temp[i] = self.state[i] + 0.5 * dt * k1[i];
276 }
277 let k2 = f.derivatives(self.t + 0.5 * dt, &y_temp);
278 if k2.iter().any(|value| !value.is_finite()) {
279 return Err(SolversError::ComputationError);
280 }
281
282 for i in 0..4 {
283 y_temp[i] = self.state[i] + 0.5 * dt * k2[i];
284 }
285 let k3 = f.derivatives(self.t + 0.5 * dt, &y_temp);
286 if k3.iter().any(|value| !value.is_finite()) {
287 return Err(SolversError::ComputationError);
288 }
289
290 for i in 0..4 {
291 y_temp[i] = self.state[i] + dt * k3[i];
292 }
293 let k4 = f.derivatives(self.t + dt, &y_temp);
294 if k4.iter().any(|value| !value.is_finite()) {
295 return Err(SolversError::ComputationError);
296 }
297
298 for i in 0..4 {
300 self.state[i] += dt * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
301 }
302 if self.state.iter().any(|value| !value.is_finite()) {
303 return Err(SolversError::ComputationError);
304 }
305
306 self.t += dt;
307
308 self.solver_state.error = self.estimate_error(&k1, &k2, &k3, &k4);
310
311 Ok(())
312 }
313
314 fn estimate_error(&self, k1: &[f64; 4], k2: &[f64; 4], k3: &[f64; 4], k4: &[f64; 4]) -> f64 {
316 let mut error: f64 = 0.0;
317
318 for i in 0..4 {
319 let k_avg = (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0;
321 let variance = (k1[i] - k_avg).powi(2)
322 + (k2[i] - k_avg).powi(2)
323 + (k3[i] - k_avg).powi(2)
324 + (k4[i] - k_avg).powi(2);
325
326 let local_error = variance.sqrt() / k_avg.abs().max(1e-10);
327 error = error.max(local_error);
328 }
329
330 error
331 }
332
333 pub fn get_state(&self) -> ODEState {
335 ODEState {
336 y: self.state,
337 t: self.t,
338 dt: self.dt,
339 }
340 }
341}
342
343impl ShootingMethodBVP {
344 pub fn new(target_values: [f64; 4], config: SolverConfig) -> Self {
346 Self {
347 initial_guess: [0.0; 4],
348 target_values,
349 trajectory: [ODEState::default(); 100],
350 boundary_error: [0.0; 4],
351 config,
352 solver_state: SolverState::default(),
353 }
354 }
355
356 pub fn solve<F>(
358 &mut self,
359 f: &F,
360 t0: f64,
361 t1: f64,
362 initial_guess: [f64; 4],
363 ) -> SolverResult<BVPState>
364 where
365 F: BVPFunction,
366 {
367 if !t0.is_finite()
368 || !t1.is_finite()
369 || t1 <= t0
370 || initial_guess.iter().any(|value| !value.is_finite())
371 || self.target_values.iter().any(|value| !value.is_finite())
372 || !self.config.tolerance.is_finite()
373 || self.config.tolerance < 0.0
374 {
375 return Err(SolversError::InvalidParameters);
376 }
377
378 self.initial_guess = initial_guess;
379 self.solver_state.iteration = 0;
380 self.solver_state.converged = false;
381
382 while self.solver_state.iteration < self.config.max_iterations {
383 self.shoot_trajectory(f, t0, t1)?;
385
386 self.calculate_boundary_error(f)?;
388 self.solver_state.iteration += 1;
389
390 let max_error = self
392 .boundary_error
393 .iter()
394 .fold(0.0_f64, |acc, &x| acc.max(x.abs()));
395 self.solver_state.error = max_error;
396
397 if max_error < self.config.tolerance {
398 self.solver_state.converged = true;
399 return Ok(BVPState {
400 iteration: self.solver_state.iteration,
401 boundary_error: self.solver_state.error,
402 converged: true,
403 shooting_params: self.initial_guess,
404 });
405 }
406
407 self.update_shooting_parameters(f, t0, t1)?;
409 }
410
411 Err(SolversError::ConvergenceFailed)
412 }
413
414 fn shoot_trajectory<F>(&mut self, f: &F, t0: f64, t1: f64) -> SolverResult<()>
416 where
417 F: BVPFunction,
418 {
419 struct BvpOdeAdapter<'a, G: BVPFunction> {
420 function: &'a G,
421 params: [f64; 4],
422 }
423 impl<G: BVPFunction> ODEFunction for BvpOdeAdapter<'_, G> {
424 fn derivatives(&self, t: f64, y: &[f64; 4]) -> [f64; 4] {
425 self.function.derivatives(t, y, &self.params)
426 }
427 }
428
429 let dt = (t1 - t0) / 99.0;
430 let mut rk4 = RungeKutta4Static::new(dt, self.config);
431 rk4.state = self.initial_guess;
432 rk4.t = t0;
433 self.trajectory[0] = rk4.get_state();
434
435 let ode_f = BvpOdeAdapter {
436 function: f,
437 params: self.initial_guess,
438 };
439 for index in 1..self.trajectory.len() {
440 rk4.rk4_step(&ode_f, dt)?;
441 self.trajectory[index] = rk4.get_state();
442 }
443
444 Ok(())
445 }
446
447 fn calculate_boundary_error<F>(&mut self, f: &F) -> SolverResult<()>
449 where
450 F: BVPFunction,
451 {
452 let initial_state = &self.trajectory[0];
454 let final_state = &self.trajectory[99];
455
456 let residual = f.boundary_residuals(&initial_state.y, &final_state.y);
458 for (index, value) in residual.iter().enumerate() {
459 self.boundary_error[index] = *value - self.target_values[index];
460 if !self.boundary_error[index].is_finite() {
461 return Err(SolversError::ComputationError);
462 }
463 }
464
465 Ok(())
466 }
467
468 fn update_shooting_parameters<F>(&mut self, f: &F, t0: f64, t1: f64) -> SolverResult<()>
470 where
471 F: BVPFunction,
472 {
473 let base_guess = self.initial_guess;
474 let base_residual = self.boundary_error;
475 let mut jacobian = [[0.0_f64; 4]; 4];
476
477 for column in 0..4 {
478 let mut perturbed = base_guess;
479 let delta = f64::EPSILON.sqrt() * (1.0 + base_guess[column].abs());
480 perturbed[column] += delta;
481 self.initial_guess = perturbed;
482 self.shoot_trajectory(f, t0, t1)?;
483 self.calculate_boundary_error(f)?;
484 for row in 0..4 {
485 jacobian[row][column] = (self.boundary_error[row] - base_residual[row]) / delta;
486 }
487 }
488 self.initial_guess = base_guess;
489 self.boundary_error = base_residual;
490
491 let rhs = base_residual.map(|value| -value);
492 let correction = solve_4x4(jacobian, rhs)?;
493 for index in 0..4 {
494 self.initial_guess[index] += correction[index];
495 if !self.initial_guess[index].is_finite() {
496 return Err(SolversError::ComputationError);
497 }
498 }
499
500 Ok(())
501 }
502
503 pub fn get_shooting_params(&self) -> [f64; 4] {
505 self.initial_guess
506 }
507}
508
509fn solve_4x4(mut matrix: [[f64; 4]; 4], mut rhs: [f64; 4]) -> SolverResult<[f64; 4]> {
510 for pivot_column in 0..4 {
511 let mut pivot_row = pivot_column;
512 let mut pivot_magnitude = matrix[pivot_row][pivot_column].abs();
513 for candidate in pivot_column + 1..4 {
514 let magnitude = matrix[candidate][pivot_column].abs();
515 if magnitude > pivot_magnitude {
516 pivot_row = candidate;
517 pivot_magnitude = magnitude;
518 }
519 }
520 if !pivot_magnitude.is_finite() || pivot_magnitude <= 64.0 * f64::EPSILON {
521 return Err(SolversError::SingularMatrix);
522 }
523 if pivot_row != pivot_column {
524 matrix.swap(pivot_row, pivot_column);
525 rhs.swap(pivot_row, pivot_column);
526 }
527
528 for row in pivot_column + 1..4 {
529 let factor = matrix[row][pivot_column] / matrix[pivot_column][pivot_column];
530 matrix[row][pivot_column] = 0.0;
531 for column in pivot_column + 1..4 {
532 matrix[row][column] -= factor * matrix[pivot_column][column];
533 }
534 rhs[row] -= factor * rhs[pivot_column];
535 }
536 }
537
538 let mut solution = [0.0_f64; 4];
539 for row in (0..4).rev() {
540 let mut value = rhs[row];
541 for column in row + 1..4 {
542 value -= matrix[row][column] * solution[column];
543 }
544 solution[row] = value / matrix[row][row];
545 if !solution[row].is_finite() {
546 return Err(SolversError::ComputationError);
547 }
548 }
549 Ok(solution)
550}
551
552impl SimpsonsIntegratorChunked {
553 pub fn new(a: f64, b: f64, config: SolverConfig) -> Self {
555 Self {
556 chunk_buffer: [0.0; 12],
557 chunk_index: 0,
558 accumulated_integral: 0.0,
559 a,
560 b,
561 chunks_processed: 0,
562 config,
563 solver_state: SolverState::default(),
564 }
565 }
566
567 pub fn integrate<F>(&mut self, f: &F) -> SolverResult<f64>
569 where
570 F: IntegrandFunction,
571 {
572 self.accumulated_integral = 0.0;
573 self.chunk_index = 0;
574 self.chunks_processed = 0;
575 self.solver_state.iteration = 0;
576
577 let total_length = self.b - self.a;
578 let chunk_size = total_length / 100.0; while self.chunk_index < 100 && self.solver_state.iteration < self.config.max_iterations {
581 let chunk_start = self.a + self.chunk_index as f64 * chunk_size;
582 let chunk_end = chunk_start + chunk_size;
583
584 let chunk_integral = self.process_chunk(f, chunk_start, chunk_end)?;
586 self.accumulated_integral += chunk_integral;
587
588 self.chunk_index += 1;
589 self.chunks_processed += 1;
590 self.solver_state.iteration += 1;
591 }
592
593 self.solver_state.converged = self.chunk_index >= 100;
594 self.solver_state.error = self.estimate_integration_error();
595
596 Ok(self.accumulated_integral)
597 }
598
599 fn process_chunk<F>(&mut self, f: &F, x_start: f64, x_end: f64) -> SolverResult<f64>
601 where
602 F: IntegrandFunction,
603 {
604 const SAMPLES: usize = 11;
607 let h = (x_end - x_start) / (SAMPLES - 1) as f64;
608
609 for i in 0..SAMPLES {
610 let x = x_start + i as f64 * h;
611 self.chunk_buffer[i] = f.evaluate(x);
612 }
613
614 let mut chunk_integral = self.chunk_buffer[0] + self.chunk_buffer[SAMPLES - 1];
616
617 for i in 1..SAMPLES - 1 {
618 let weight = if i % 2 == 1 { 4.0 } else { 2.0 };
619 chunk_integral += weight * self.chunk_buffer[i];
620 }
621
622 chunk_integral *= h / 3.0;
623
624 Ok(chunk_integral)
625 }
626
627 fn estimate_integration_error(&self) -> f64 {
629 if self.chunk_index < 100 {
631 let remaining_chunks = 100 - self.chunk_index;
632 remaining_chunks as f64 / 100.0
633 } else {
634 0.0
635 }
636 }
637
638 pub fn get_integral(&self) -> f64 {
640 self.accumulated_integral
641 }
642}
643
644impl Default for ODEState {
645 fn default() -> Self {
646 Self {
647 y: [0.0; 4],
648 t: 0.0,
649 dt: 0.01,
650 }
651 }
652}
653
654impl Default for BVPState {
655 fn default() -> Self {
656 Self {
657 iteration: 0,
658 boundary_error: f64::MAX,
659 converged: false,
660 shooting_params: [0.0; 4],
661 }
662 }
663}
664
665impl Default for IntegralChunk {
666 fn default() -> Self {
667 Self {
668 values: [0.0; 12],
669 x_coords: [0.0; 12],
670 contribution: 0.0,
671 }
672 }
673}
674
675impl Default for RungeKutta4Static {
676 fn default() -> Self {
677 Self::new(0.01, SolverConfig::default())
678 }
679}
680
681impl Default for ShootingMethodBVP {
682 fn default() -> Self {
683 Self::new([0.0; 4], SolverConfig::default())
684 }
685}
686
687impl Default for SimpsonsIntegratorChunked {
688 fn default() -> Self {
689 Self::new(0.0, 1.0, SolverConfig::default())
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696
697 struct ExponentialDecay;
699
700 impl ODEFunction for ExponentialDecay {
701 fn derivatives(&self, _t: f64, y: &[f64; 4]) -> [f64; 4] {
702 [-y[0], -y[1], -y[2], -y[3]]
703 }
704 }
705
706 #[test]
707 fn test_rk4_static() {
708 let mut rk4 = RungeKutta4Static::new(0.1, SolverConfig::default());
709 let decay = ExponentialDecay;
710
711 let result = rk4.integrate(&decay, 0.0, [1.0; 4], 1.0);
712 assert!(result.is_ok());
713
714 let state = result.unwrap();
715 assert!((state.y[0] - 0.3679).abs() < 0.01);
717 assert_eq!(state.t, 1.0);
718 }
719
720 #[test]
721 fn final_time_integration_does_not_stop_at_a_steady_state() {
722 struct Stationary;
723 impl ODEFunction for Stationary {
724 fn derivatives(&self, _t: f64, _y: &[f64; 4]) -> [f64; 4] {
725 [0.0; 4]
726 }
727 }
728
729 let mut rk4 = RungeKutta4Static::new(0.1, SolverConfig::default());
730 let state = rk4.integrate(&Stationary, 0.0, [2.0; 4], 1.0).unwrap();
731 assert_eq!(state.t, 1.0);
732 assert_eq!(state.y, [2.0; 4]);
733
734 let steady = rk4
735 .integrate_until_steady_state(&Stationary, 0.0, [2.0; 4], 1.0)
736 .unwrap();
737 assert_eq!(steady.t, 0.0);
738 }
739
740 #[test]
741 fn static_rk4_rejects_invalid_domains_and_iteration_exhaustion() {
742 let mut invalid = RungeKutta4Static::new(0.0, SolverConfig::default());
743 assert_eq!(
744 invalid.integrate(&ExponentialDecay, 0.0, [1.0; 4], 1.0),
745 Err(SolversError::InvalidParameters)
746 );
747
748 let mut config = SolverConfig::default();
749 config.max_iterations = 1;
750 let mut exhausted = RungeKutta4Static::new(0.1, config);
751 assert_eq!(
752 exhausted.integrate(&ExponentialDecay, 0.0, [1.0; 4], 1.0),
753 Err(SolversError::ConvergenceFailed)
754 );
755 }
756
757 #[test]
758 fn test_shooting_method_bvp() {
759 let mut bvp = ShootingMethodBVP::new([0.0; 4], SolverConfig::default());
760
761 struct DecayBVP;
763
764 impl BVPFunction for DecayBVP {
765 fn derivatives(&self, _t: f64, y: &[f64; 4], _params: &[f64; 4]) -> [f64; 4] {
766 [-y[0], -y[1], -y[2], -y[3]]
767 }
768
769 fn boundary_residuals(&self, y0: &[f64; 4], y1: &[f64; 4]) -> [f64; 4] {
770 [y0[0] - 1.0, y1[0] - (-1.0_f64).exp(), 0.0, 0.0]
771 }
772 }
773
774 let decay = DecayBVP;
775 let result = bvp.solve(&decay, 0.0, 1.0, [1.0, 0.0, 0.0, 0.0]);
776 assert!(result.is_ok());
777
778 let state = result.unwrap();
779 assert!(state.iteration > 0);
780 assert!(state.converged);
781 assert_eq!(bvp.trajectory[0].y, [1.0, 0.0, 0.0, 0.0]);
782 assert!((bvp.trajectory[99].t - 1.0).abs() < 1e-12);
783 }
784
785 #[test]
786 fn shooting_method_uses_a_real_newton_correction() {
787 struct ConstantBvp;
788 impl BVPFunction for ConstantBvp {
789 fn derivatives(&self, _t: f64, _y: &[f64; 4], _params: &[f64; 4]) -> [f64; 4] {
790 [0.0; 4]
791 }
792
793 fn boundary_residuals(&self, y0: &[f64; 4], _y1: &[f64; 4]) -> [f64; 4] {
794 *y0
795 }
796 }
797
798 let target = [1.0, -2.0, 3.5, 4.25];
799 let mut bvp = ShootingMethodBVP::new(target, SolverConfig::default());
800 let result = bvp.solve(&ConstantBvp, 0.0, 1.0, [0.0; 4]).unwrap();
801 assert!(result.converged);
802 for (actual, expected) in result.shooting_params.iter().zip(target) {
803 assert!((actual - expected).abs() < 1e-10);
804 }
805 }
806
807 #[test]
808 fn test_simpsons_integrator_chunked() {
809 let mut integrator =
810 SimpsonsIntegratorChunked::new(0.0, std::f64::consts::PI, SolverConfig::default());
811
812 struct SinFunction;
814
815 impl IntegrandFunction for SinFunction {
816 fn evaluate(&self, x: f64) -> f64 {
817 x.sin()
818 }
819 }
820
821 let sin_func = SinFunction;
822 let result = integrator.integrate(&sin_func);
823 assert!(result.is_ok());
824
825 let integral = result.unwrap();
826 println!("INTEGRAL VALUE: {}", integral);
827 assert!((integral - 2.0).abs() < 0.1);
829 }
830
831 #[test]
832 fn test_zero_allocation_guarantee() {
833 }
837}