Files
rustytorch/crates/specialized/rtx-fea/tests/sparse_tangent_bench.rs
T
Omar SobhandClaude Opus 5.5 860f5bb37f R8-g: sparse supernodal LDLt for the Newton tangent (default off)
rtx_fea::solvers::SparseLdlt: nested-dissection ordering, etree,
fundamental supernodes, multifrontal numeric factorisation (blocked
LDLt, matrixmultiply gemm, rayon over subtrees, bit-deterministic at
any thread count), symbolic analysis reused while the pattern holds.

NonlinearDynamicStepper: TangentSolver::{BandedLu (default, unchanged
float for float), SparseLdlt { reuse }} via with_tangent_solver or
RTX_FEA_TANGENT=sparse[:K]; fixed-pattern CSR assembled from parallel
element evaluations (forces summed in the banded path's order);
optional modified Newton (factor reuse). The per-element kernel is
factored out of assemble() unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 22:28:53 -05:00

90 lines
3.8 KiB
Rust

//! R8-g instrument: the sparse LDLᵀ against the banded LU on the dumped
//! 3-D flag operators (R8-b's `k_*.coo` / `m_*.coo`, the TL tangent at
//! u = 0 and the consistent mass on the free DOFs). The Newmark tangent
//! `K + M/(β Δt²)` at CSM3's Δt = 0.005 is solved by both; the timing and
//! the agreement are printed.
//!
//! `R8G_COO_DIR` (default the R8-b study dir), `R8G_TAGS` (comma list).
use nalgebra::DVector;
use rtx_fea::assembly::SparseMatrix;
use rtx_fea::solvers::{BandedLu, LinearSolver, SolverOptions, SparseLdlt};
use std::time::Instant;
fn read_coo(path: &str) -> Vec<(usize, usize, f64)> {
let text = std::fs::read_to_string(path).unwrap_or_else(|e| panic!("{path}: {e}"));
text.lines()
.filter(|l| !l.trim().is_empty())
.map(|l| {
let mut it = l.split_whitespace();
let i: usize = it.next().unwrap().parse().unwrap();
let j: usize = it.next().unwrap().parse().unwrap();
let v: f64 = it.next().unwrap().parse().unwrap();
(i, j, v)
})
.collect()
}
#[test]
#[ignore = "instrument: sparse LDLt vs banded LU on the dumped flag tangents"]
fn ldlt_vs_banded_on_dumped_tangents() {
let dir = std::env::var("R8G_COO_DIR").unwrap_or_else(|_| {
"/home/osobh/projects/omni-cortex-data/fsi_studies/p1_regress/r8b".to_string()
});
let tags = std::env::var("R8G_TAGS")
.unwrap_or_else(|_| "35x2x1_s0.05_ps,35x2x8_s0.41_free".to_string());
let skip_banded = std::env::var("R8G_SKIP_BANDED").is_ok();
let coef = 1.0 / (0.25 * 0.005 * 0.005);
for tag in tags.split(',') {
let k = read_coo(&format!("{dir}/k_{tag}.coo"));
let m = read_coo(&format!("{dir}/m_{tag}.coo"));
let n = k.iter().map(|t| t.0.max(t.1)).max().unwrap() + 1;
let mut triplets = k.clone();
triplets.extend(m.iter().map(|&(i, j, v)| (i, j, coef * v)));
let a = SparseMatrix::from_triplets(n, n, &triplets).unwrap();
let x_exact = DVector::from_fn(n, |i, _| ((i as f64) * 0.37).sin() + 0.2);
let b = a.multiply_vector(&x_exact).unwrap();
let opts = SolverOptions::default();
let mut ldlt = SparseLdlt::new();
let t0 = Instant::now();
let (row_ptr, col_idx) = a.structure();
ldlt.analyze(n, row_ptr, col_idx).unwrap();
let t_sym = t0.elapsed().as_secs_f64();
let mut t_num = f64::MAX;
let mut t_solve = f64::MAX;
let mut x = DVector::zeros(n);
for _ in 0..5 {
let t1 = Instant::now();
ldlt.factorize_values(a.values()).unwrap();
t_num = t_num.min(t1.elapsed().as_secs_f64());
let t2 = Instant::now();
x = ldlt.solve_factored(&b).unwrap();
t_solve = t_solve.min(t2.elapsed().as_secs_f64());
}
let stats = ldlt.stats().unwrap();
let rel_exact = (&x - &x_exact).norm() / x_exact.norm();
let residual = (a.multiply_vector(&x).unwrap() - &b).norm() / b.norm();
let (t_band, rel_band) = if skip_banded {
(f64::NAN, f64::NAN)
} else {
let t3 = Instant::now();
let (xb, _) = BandedLu::new().solve(&a, &b, &opts).unwrap();
let t = t3.elapsed().as_secs_f64();
(t, (&x - &xb).norm() / xb.norm())
};
println!(
"{tag}: n {n} nnz(A) {} | LDLt symbolic {t_sym:.3} s, numeric {t_num:.4} s, solve \
{t_solve:.4} s | nnz(L) {} supernodes {} max front {} work {:.3e} | banded LU \
{t_band:.3} s | rel vs exact {rel_exact:.2e}, vs banded {rel_band:.2e}, residual \
{residual:.2e} | threads {}",
a.nnz(),
stats.nnz_l,
stats.supernodes,
stats.max_front,
stats.work,
rayon::current_num_threads()
);
}
}