Skip to content

Commit a5547f9

Browse files
committed
Ver 0.39.9
- Change an implementation of GL4
2 parents 7460bbc + 84b682b commit a5547f9

File tree

5 files changed

+102
-71
lines changed

5 files changed

+102
-71
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.8"
3+
version = "0.39.9"
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.9 (2025-07-10)
2+
3+
- Change implementation of Gauss-Legendre 4th order method
4+
15
# Release 0.39.8 (2025-06-23)
26

37
- Implement `LogNormal` distribution

example_data/gl4_test.png

-369 Bytes
Loading

example_data/lorenz_gl4.png

57.8 KB
Loading

src/numerical/ode.rs

Lines changed: 97 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,18 @@ impl ButcherTableau for RKF78 {
11551155
// ┌─────────────────────────────────────────────────────────┐
11561156
// Gauss-Legendre 4th order
11571157
// └─────────────────────────────────────────────────────────┘
1158+
1159+
// Correct coefficients for 4th-order Gauss-Legendre method
1160+
const SQRT3: f64 = 1.7320508075688772;
1161+
const C1: f64 = 0.5 - SQRT3 / 6.0;
1162+
const C2: f64 = 0.5 + SQRT3 / 6.0;
1163+
const A11: f64 = 0.25;
1164+
const A12: f64 = 0.25 - SQRT3 / 6.0;
1165+
const A21: f64 = 0.25 + SQRT3 / 6.0;
1166+
const A22: f64 = 0.25;
1167+
const B1: f64 = 0.5;
1168+
const B2: f64 = 0.5;
1169+
11581170
/// Enum for implicit solvers.
11591171
///
11601172
/// This enum defines the available implicit solvers for the Gauss-Legendre 4th order integrator.
@@ -1198,7 +1210,7 @@ impl Default for GL4 {
11981210
fn default() -> Self {
11991211
GL4 {
12001212
solver: ImplicitSolver::FixedPoint,
1201-
tol: 1e-6,
1213+
tol: 1e-8,
12021214
max_step_iter: 100,
12031215
}
12041216
}
@@ -1219,41 +1231,40 @@ impl ODEIntegrator for GL4 {
12191231
#[inline]
12201232
fn step<P: ODEProblem>(&self, problem: &P, t: f64, y: &mut [f64], dt: f64) -> Result<f64> {
12211233
let n = y.len();
1222-
let sqrt3 = 3.0_f64.sqrt();
1223-
let c = 0.5 * (3.0 - sqrt3) / 6.0;
1224-
let d = 0.5 * (3.0 + sqrt3) / 6.0;
1234+
//let sqrt3 = 3.0_f64.sqrt();
1235+
//let c = 0.5 * (3.0 - sqrt3) / 6.0;
1236+
//let d = 0.5 * (3.0 + sqrt3) / 6.0;
12251237
let mut k1 = vec![0.0; n];
12261238
let mut k2 = vec![0.0; n];
1227-
let mut y1 = vec![0.0; n];
1228-
let mut y2 = vec![0.0; n];
1239+
1240+
// Initial guess for k1, k2.
1241+
problem.rhs(t, y, &mut k1)?;
1242+
k2.copy_from_slice(&k1);
12291243

12301244
match self.solver {
12311245
ImplicitSolver::FixedPoint => {
12321246
// Fixed-point iteration
1247+
let mut y1 = vec![0.0; n];
1248+
let mut y2 = vec![0.0; n];
1249+
12331250
for _ in 0..self.max_step_iter {
1251+
let k1_old = k1.clone();
1252+
let k2_old = k2.clone();
1253+
12341254
for i in 0..n {
1235-
y1[i] = y[i] + dt * (c * k1[i] + d * k2[i] - sqrt3 * (k2[i] - k1[i]) / 2.0);
1236-
y2[i] = y[i] + dt * (c * k1[i] + d * k2[i] + sqrt3 * (k2[i] - k1[i]) / 2.0);
1255+
y1[i] = y[i] + dt * (A11 * k1[i] + A12 * k2[i]);
1256+
y2[i] = y[i] + dt * (A11 * k1[i] + A12 * k2[i]);
12371257
}
12381258

1239-
problem.rhs(t + c * dt, &y1, &mut k1)?;
1240-
problem.rhs(t + d * dt, &y2, &mut k2)?;
1259+
// Compute new k1 and k2
1260+
problem.rhs(t + C1 * dt, &y1, &mut k1)?;
1261+
problem.rhs(t + C2 * dt, &y2, &mut k2)?;
12411262

1263+
// Check for convergence
12421264
let mut max_diff = 0f64;
12431265
for i in 0..n {
1244-
max_diff = max_diff
1245-
.max(
1246-
(y1[i]
1247-
- y[i]
1248-
- dt * (c * k1[i] + d * k2[i] - sqrt3 * (k2[i] - k1[i]) / 2.0))
1249-
.abs(),
1250-
)
1251-
.max(
1252-
(y2[i]
1253-
- y[i]
1254-
- dt * (c * k1[i] + d * k2[i] + sqrt3 * (k2[i] - k1[i]) / 2.0))
1255-
.abs(),
1256-
);
1266+
max_diff = max_diff.max((k1[i] - k1_old[i]).abs());
1267+
max_diff = max_diff.max((k2[i] - k2_old[i]).abs());
12571268
}
12581269

12591270
if max_diff < self.tol {
@@ -1264,43 +1275,28 @@ impl ODEIntegrator for GL4 {
12641275
ImplicitSolver::Broyden => {
12651276
let m = 2 * n;
12661277
let mut U = vec![0.0; m];
1267-
1268-
// Initial Guess via Fixed-Point Iteration
1269-
{
1270-
let mut k1 = vec![0.0; n];
1271-
let mut k2 = vec![0.0; n];
1272-
let mut y1 = vec![0.0; n];
1273-
let mut y2 = vec![0.0; n];
1274-
1275-
y1.copy_from_slice(y);
1276-
y2.copy_from_slice(y);
1277-
problem.rhs(t + c * dt, &y1, &mut k1)?;
1278-
problem.rhs(t + d * dt, &y2, &mut k2)?;
1279-
for i in 0..n {
1280-
U[i] = k1[i];
1281-
U[n + i] = k2[i];
1282-
}
1283-
}
1278+
U[..n].copy_from_slice(&k1);
1279+
U[n..].copy_from_slice(&k2);
12841280

12851281
// F_vec = F(U)
12861282
let mut F_vec = vec![0.0; m];
1287-
compute_F(problem, t, y, dt, c, d, sqrt3, &U, &mut F_vec)?;
1283+
compute_F(problem, t, y, dt, &U, &mut F_vec)?;
12881284

12891285
// Initialize inverse Jacobian matrix
12901286
let mut J_inv = eye(m);
12911287

12921288
// Repeat Broyden's method
12931289
for _ in 0..self.max_step_iter {
12941290
// delta = - J_inv * F_vec
1295-
let mut delta = (&J_inv * &F_vec).mul_scalar(-1.0);
1291+
let delta = (&J_inv * &F_vec).mul_scalar(-1.0);
12961292

12971293
// U <- U + delta
12981294
U.iter_mut()
1299-
.zip(delta.iter_mut())
1295+
.zip(delta.iter())
13001296
.for_each(|(u, d)| *u += *d);
13011297

13021298
let mut F_new = vec![0.0; m];
1303-
compute_F(problem, t, y, dt, c, d, sqrt3, &U, &mut F_new)?;
1299+
compute_F(problem, t, y, dt, &U, &mut F_new)?;
13041300

13051301
// If infinity norm of F_new is less than tol, break
13061302
if F_new.norm(Norm::LInf) < self.tol {
@@ -1312,12 +1308,13 @@ impl ODEIntegrator for GL4 {
13121308

13131309
// J_inv * delta_F
13141310
let J_inv_delta_F = &J_inv * &delta_F;
1311+
13151312
let denom = delta.dot(&J_inv_delta_F);
13161313
if denom.abs() < 1e-12 {
13171314
break;
13181315
}
13191316

1320-
// Broyden "good" update
1317+
// Broyden's "good" update for the inverse Jacobian
13211318
// J_inv <- J_inv + ((delta - J_inv * delta_F) * delta^T * J_inv) / denom
13221319
let delta_minus_J_inv_delta_F = delta.sub_vec(&J_inv_delta_F).to_col();
13231320
let delta_T_J_inv = &delta.to_row() * &J_inv;
@@ -1326,57 +1323,87 @@ impl ODEIntegrator for GL4 {
13261323
F_vec = F_new;
13271324
}
13281325

1329-
// Extract k1 and k2
1330-
for i in 0..n {
1331-
k1[i] = U[i];
1332-
k2[i] = U[n + i];
1333-
}
1326+
k1.copy_from_slice(&U[..n]);
1327+
k2.copy_from_slice(&U[n..]);
13341328
}
13351329
}
13361330

13371331
for i in 0..n {
1338-
y[i] += dt * 0.5 * (k1[i] + k2[i]);
1332+
y[i] += dt * (B1 * k1[i] + B2 * k2[i]);
13391333
}
13401334

13411335
Ok(dt)
13421336
}
13431337
}
13441338

1345-
// Helper function to compute the function F(U) for the implicit solver.
1346-
// y1 = y + dt*(c*k1 + d*k2 - sqrt3/2*(k2-k1))
1347-
// y2 = y + dt*(c*k1 + d*k2 + sqrt3/2*(k2-k1))
1339+
//// Helper function to compute the function F(U) for the implicit solver.
1340+
//// y1 = y + dt*(c*k1 + d*k2 - sqrt3/2*(k2-k1))
1341+
//// y2 = y + dt*(c*k1 + d*k2 + sqrt3/2*(k2-k1))
1342+
//#[allow(non_snake_case)]
1343+
//fn compute_F<P: ODEProblem>(
1344+
// problem: &P,
1345+
// t: f64,
1346+
// y: &[f64],
1347+
// dt: f64,
1348+
// c: f64,
1349+
// d: f64,
1350+
// sqrt3: f64,
1351+
// U: &[f64],
1352+
// F: &mut [f64],
1353+
//) -> Result<()> {
1354+
// let n = y.len();
1355+
// let mut y1 = vec![0.0; n];
1356+
// let mut y2 = vec![0.0; n];
1357+
//
1358+
// for i in 0..n {
1359+
// let k1 = U[i];
1360+
// let k2 = U[n + i];
1361+
// y1[i] = y[i] + dt * (c * k1 + d * k2 - sqrt3 * (k2 - k1) / 2.0);
1362+
// y2[i] = y[i] + dt * (c * k1 + d * k2 + sqrt3 * (k2 - k1) / 2.0);
1363+
// }
1364+
//
1365+
// let mut f1 = vec![0.0; n];
1366+
// let mut f2 = vec![0.0; n];
1367+
// problem.rhs(t + c * dt, &y1, &mut f1)?;
1368+
// problem.rhs(t + d * dt, &y2, &mut f2)?;
1369+
//
1370+
// // F = [ k1 - f1, k2 - f2 ]
1371+
// for i in 0..n {
1372+
// F[i] = U[i] - f1[i];
1373+
// F[n + i] = U[n + i] - f2[i];
1374+
// }
1375+
// Ok(())
1376+
//}
1377+
/// Helper function to compute the residual F(U) = U - f(y + dt*A*U)
13481378
#[allow(non_snake_case)]
13491379
fn compute_F<P: ODEProblem>(
13501380
problem: &P,
13511381
t: f64,
13521382
y: &[f64],
13531383
dt: f64,
1354-
c: f64,
1355-
d: f64,
1356-
sqrt3: f64,
1357-
U: &[f64],
1384+
U: &[f64], // U is a concatenated vector [k1, k2]
13581385
F: &mut [f64],
13591386
) -> Result<()> {
13601387
let n = y.len();
1388+
let (k1_slice, k2_slice) = U.split_at(n);
1389+
13611390
let mut y1 = vec![0.0; n];
13621391
let mut y2 = vec![0.0; n];
13631392

13641393
for i in 0..n {
1365-
let k1 = U[i];
1366-
let k2 = U[n + i];
1367-
y1[i] = y[i] + dt * (c * k1 + d * k2 - sqrt3 * (k2 - k1) / 2.0);
1368-
y2[i] = y[i] + dt * (c * k1 + d * k2 + sqrt3 * (k2 - k1) / 2.0);
1394+
y1[i] = y[i] + dt * (A11 * k1_slice[i] + A12 * k2_slice[i]);
1395+
y2[i] = y[i] + dt * (A21 * k1_slice[i] + A22 * k2_slice[i]);
13691396
}
1397+
1398+
// F is an output parameter, its parts f1 and f2 are stored temporarily
1399+
let (f1, f2) = F.split_at_mut(n);
1400+
problem.rhs(t + C1 * dt, &y1, f1)?;
1401+
problem.rhs(t + C2 * dt, &y2, f2)?;
13701402

1371-
let mut f1 = vec![0.0; n];
1372-
let mut f2 = vec![0.0; n];
1373-
problem.rhs(t + c * dt, &y1, &mut f1)?;
1374-
problem.rhs(t + d * dt, &y2, &mut f2)?;
1375-
1376-
// F = [ k1 - f1, k2 - f2 ]
1403+
// Compute final residual F = [k1 - f1, k2 - f2]
13771404
for i in 0..n {
1378-
F[i] = U[i] - f1[i];
1379-
F[n + i] = U[n + i] - f2[i];
1405+
f1[i] = k1_slice[i] - f1[i];
1406+
f2[i] = k2_slice[i] - f2[i];
13801407
}
13811408
Ok(())
13821409
}

0 commit comments

Comments
 (0)