Skip to content

Commit 5e76ad1

Browse files
committed
Ver 0.39.2
- Implement Broyden method for GL4
2 parents a61f8a4 + 0bfb031 commit 5e76ad1

File tree

4 files changed

+121
-5
lines changed

4 files changed

+121
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "peroxide"
3-
version = "0.39.1"
3+
version = "0.39.2"
44
authors = ["axect <axect@outlook.kr>"]
55
edition = "2018"
66
description = "Rust comprehensive scientific computation library contains linear algebra, numerical analysis, statistics and machine learning tools with farmiliar syntax"

RELEASES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Release 0.39.2 (2025-02-06)
2+
3+
- Implement `Broyden` method for `GL4`
4+
15
# Release 0.39.1 (2025-02-06)
26

37
- Add `lambert_w` doc for crate docs [#82](https://github.com/Axect/Peroxide/pull/82) (Thanks to [@JSorngard](https://github.com/JSorngard))

examples/ode_test_orbit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1414

1515
let problem = KeplerProblem;
1616
let gl4 = GL4 {
17-
solver: ImplicitSolver::FixedPoint,
17+
solver: ImplicitSolver::Broyden,
1818
tol: 1e-10,
1919
max_step_iter: 100,
2020
};

src/numerical/ode.rs

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@
8686
//! ```
8787
8888
use anyhow::{bail, Result};
89+
use crate::fuga::ConvToMat;
90+
use crate::util::non_macro::eye;
91+
use crate::traits::math::{Norm, Normed, InnerProduct, Vector};
8992

9093
/// Trait for defining an ODE problem.
9194
///
@@ -890,12 +893,12 @@ impl ButcherTableau for TSIT45 {
890893
/// Enum for implicit solvers.
891894
///
892895
/// This enum defines the available implicit solvers for the Gauss-Legendre 4th order integrator.
893-
/// Currently, only the fixed-point iteration method is implemented.
896+
/// Currently, there are two options: fixed-point iteration and Broyden's method.
894897
#[derive(Debug, Clone, Copy)]
895898
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
896899
pub enum ImplicitSolver {
897900
FixedPoint,
898-
//Broyden,
901+
Broyden,
899902
//TrustRegion(f64, f64),
900903
}
901904

@@ -939,6 +942,7 @@ impl GL4 {
939942
}
940943

941944
impl ODEIntegrator for GL4 {
945+
#[allow(non_snake_case)]
942946
#[inline]
943947
fn step<P: ODEProblem>(&self, problem: &P, t: f64, y: &mut [f64], dt: f64) -> Result<f64> {
944948
let n = y.len();
@@ -983,7 +987,76 @@ impl ODEIntegrator for GL4 {
983987
break;
984988
}
985989
}
986-
}
990+
},
991+
ImplicitSolver::Broyden => {
992+
let m = 2 * n;
993+
let mut U = vec![0.0; m];
994+
995+
// Initial Guess via Fixed-Point Iteration
996+
{
997+
let mut k1 = vec![0.0; n];
998+
let mut k2 = vec![0.0; n];
999+
let mut y1 = vec![0.0; n];
1000+
let mut y2 = vec![0.0; n];
1001+
1002+
y1.copy_from_slice(y);
1003+
y2.copy_from_slice(y);
1004+
problem.rhs(t + c * dt, &y1, &mut k1)?;
1005+
problem.rhs(t + d * dt, &y2, &mut k2)?;
1006+
for i in 0 .. n {
1007+
U[i] = k1[i];
1008+
U[n + i] = k2[i];
1009+
}
1010+
}
1011+
1012+
// F_vec = F(U)
1013+
let mut F_vec = vec![0.0; m];
1014+
compute_F(problem, t, y, dt, c, d, sqrt3, &U, &mut F_vec)?;
1015+
1016+
// Initialize inverse Jacobian matrix
1017+
let mut J_inv = eye(m);
1018+
1019+
// Repeat Broyden's method
1020+
for _ in 0 .. self.max_step_iter {
1021+
// delta = - J_inv * F_vec
1022+
let mut delta = (&J_inv * &F_vec).mul_scalar(-1.0);
1023+
1024+
// U <- U + delta
1025+
U.iter_mut().zip(delta.iter_mut()).for_each(|(u, d)| *u += *d);
1026+
1027+
let mut F_new = vec![0.0; m];
1028+
compute_F(problem, t, y, dt, c, d, sqrt3, &U, &mut F_new)?;
1029+
1030+
// If infinity norm of F_new is less than tol, break
1031+
if F_new.norm(Norm::LInf) < self.tol {
1032+
break;
1033+
}
1034+
1035+
// Residual: delta_F = F_new - F_vec
1036+
let delta_F = F_new.sub_vec(&F_vec);
1037+
1038+
// J_inv * delta_F
1039+
let J_inv_delta_F = &J_inv * &delta_F;
1040+
let denom = delta.dot(&J_inv_delta_F);
1041+
if denom.abs() < 1e-12 {
1042+
break;
1043+
}
1044+
1045+
// Broyden "good" update
1046+
// J_inv <- J_inv + ((delta - J_inv * delta_F) * delta^T * J_inv) / denom
1047+
let delta_minus_J_inv_delta_F = delta.sub_vec(&J_inv_delta_F).to_col();
1048+
let delta_T_J_inv = &delta.to_row() * &J_inv;
1049+
let update = (delta_minus_J_inv_delta_F * delta_T_J_inv) / denom;
1050+
J_inv = J_inv + update;
1051+
F_vec = F_new;
1052+
}
1053+
1054+
// Extract k1 and k2
1055+
for i in 0 .. n {
1056+
k1[i] = U[i];
1057+
k2[i] = U[n + i];
1058+
}
1059+
},
9871060
}
9881061

9891062
for i in 0..n {
@@ -993,3 +1066,42 @@ impl ODEIntegrator for GL4 {
9931066
Ok(dt)
9941067
}
9951068
}
1069+
1070+
// Helper function to compute the function F(U) for the implicit solver.
1071+
// y1 = y + dt*(c*k1 + d*k2 - sqrt3/2*(k2-k1))
1072+
// y2 = y + dt*(c*k1 + d*k2 + sqrt3/2*(k2-k1))
1073+
#[allow(non_snake_case)]
1074+
fn compute_F<P: ODEProblem>(
1075+
problem: &P,
1076+
t: f64,
1077+
y: &[f64],
1078+
dt: f64,
1079+
c: f64,
1080+
d: f64,
1081+
sqrt3: f64,
1082+
U: &[f64],
1083+
F: &mut [f64],
1084+
) -> Result<()> {
1085+
let n = y.len();
1086+
let mut y1 = vec![0.0; n];
1087+
let mut y2 = vec![0.0; n];
1088+
1089+
for i in 0..n {
1090+
let k1 = U[i];
1091+
let k2 = U[n + i];
1092+
y1[i] = y[i] + dt * (c * k1 + d * k2 - sqrt3 * (k2 - k1) / 2.0);
1093+
y2[i] = y[i] + dt * (c * k1 + d * k2 + sqrt3 * (k2 - k1) / 2.0);
1094+
}
1095+
1096+
let mut f1 = vec![0.0; n];
1097+
let mut f2 = vec![0.0; n];
1098+
problem.rhs(t + c * dt, &y1, &mut f1)?;
1099+
problem.rhs(t + d * dt, &y2, &mut f2)?;
1100+
1101+
// F = [ k1 - f1, k2 - f2 ]
1102+
for i in 0..n {
1103+
F[i] = U[i] - f1[i];
1104+
F[n + i] = U[n + i] - f2[i];
1105+
}
1106+
Ok(())
1107+
}

0 commit comments

Comments
 (0)