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]>
90 lines
3.8 KiB
Rust
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()
|
|
);
|
|
}
|
|
}
|