//! 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() ); } }