use crate::ffi::{ma57ad_, ma57bd_, ma57cd_, ma57ed_, ma57id_, openblas_set_num_threads};
use pounce_common::options_list::OptionsList;
use pounce_common::types::{Index, Number};
use pounce_linsol::{EMatrixFormat, ESymSolverStatus, SparseSymLinearSolverInterface};
use std::sync::Once;
fn configure_openblas_threads_once() {
static ONCE: Once = Once::new();
ONCE.call_once(|| {
if std::env::var_os("OPENBLAS_NUM_THREADS").is_some() {
return;
}
unsafe { openblas_set_num_threads(1) };
});
}
#[derive(Debug, Clone, Copy)]
pub struct Options {
print_level: Index,
pivtol: Number,
pivtolmax: Number,
pre_alloc: Number,
pivot_order: Index,
automatic_scaling: bool,
block_size: Index,
node_amalgamation: Index,
small_pivot_flag: Index,
}
impl Options {
pub fn from_options_list(opts: &OptionsList, prefix: &str) -> Self {
let print_level = opts
.get_integer_value("ma57_print_level", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(0);
let pivtol = opts
.get_numeric_value("ma57_pivtol", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(1e-8);
let pivtolmax_default = pivtol.max(1e-4);
let pivtolmax = opts
.get_numeric_value("ma57_pivtolmax", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(pivtolmax_default)
.max(pivtol);
let pre_alloc = opts
.get_numeric_value("ma57_pre_alloc", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(1.05);
let pivot_order = opts
.get_integer_value("ma57_pivot_order", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(5);
let automatic_scaling = opts
.get_bool_value("ma57_automatic_scaling", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(false);
let block_size = opts
.get_integer_value("ma57_block_size", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(16);
let node_amalgamation = opts
.get_integer_value("ma57_node_amalgamation", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(16);
let small_pivot_flag = opts
.get_integer_value("ma57_small_pivot_flag", prefix)
.ok()
.map(|(v, _)| v)
.unwrap_or(0);
Self {
print_level,
pivtol,
pivtolmax,
pre_alloc,
pivot_order,
automatic_scaling,
block_size,
node_amalgamation,
small_pivot_flag,
}
}
pub fn defaults() -> Self {
Self {
print_level: 0,
pivtol: 1e-8,
pivtolmax: 1e-4,
pre_alloc: 1.05,
pivot_order: 5,
automatic_scaling: false,
block_size: 16,
node_amalgamation: 16,
small_pivot_flag: 0,
}
}
}
pub struct Ma57SolverInterface {
options: Options,
negevals: Index,
pivtol_changed: bool,
refactorize: bool,
initialized: bool,
dim: Index,
nonzeros: Index,
a: Vec<Number>,
icntl: [Index; 20],
cntl: [Number; 5],
info: [Index; 40],
rinfo: [Number; 20],
lkeep: Index,
keep: Vec<Index>,
iwork: Vec<Index>,
lfact: Index,
fact: Vec<Number>,
lifact: Index,
ifact: Vec<Index>,
}
impl std::fmt::Debug for Ma57SolverInterface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Ma57SolverInterface")
.field("dim", &self.dim)
.field("nonzeros", &self.nonzeros)
.field("initialized", &self.initialized)
.field("negevals", &self.negevals)
.field("pivtol", &self.options.pivtol)
.field("pivtolmax", &self.options.pivtolmax)
.finish_non_exhaustive()
}
}
impl Ma57SolverInterface {
pub fn with_options(opts: Options) -> Self {
configure_openblas_threads_once();
let mut me = Self {
options: opts,
negevals: 0,
pivtol_changed: false,
refactorize: false,
initialized: false,
dim: 0,
nonzeros: 0,
a: Vec::new(),
icntl: [0; 20],
cntl: [0.0; 5],
info: [0; 40],
rinfo: [0.0; 20],
lkeep: 0,
keep: Vec::new(),
iwork: Vec::new(),
lfact: 0,
fact: Vec::new(),
lifact: 0,
ifact: Vec::new(),
};
me.apply_icntl();
me
}
pub fn new() -> Self {
Self::with_options(Options::defaults())
}
pub fn from_options_list(opts: &OptionsList, prefix: &str) -> Self {
Self::with_options(Options::from_options_list(opts, prefix))
}
pub fn pivtol(&self) -> Number {
self.options.pivtol
}
fn apply_icntl(&mut self) {
unsafe {
ma57id_(self.cntl.as_mut_ptr(), self.icntl.as_mut_ptr());
}
self.icntl[0] = 0; self.icntl[1] = 0; self.icntl[3] = 1; self.icntl[4] = self.options.print_level;
self.icntl[5] = self.options.pivot_order;
self.icntl[6] = 1; self.icntl[10] = self.options.block_size;
self.icntl[11] = self.options.node_amalgamation;
self.icntl[14] = if self.options.automatic_scaling { 1 } else { 0 };
self.icntl[15] = self.options.small_pivot_flag;
self.cntl[0] = self.options.pivtol;
}
fn symbolic_factorization(&mut self, irn: &[Index], jcn: &[Index]) -> ESymSolverStatus {
let n = self.dim;
let ne = self.nonzeros;
self.lkeep = 5 * n + ne + n.max(ne) + 42;
self.cntl[0] = self.options.pivtol;
self.iwork = vec![0; (5 * n) as usize];
self.keep = vec![0; self.lkeep as usize];
unsafe {
ma57ad_(
&n,
&ne,
irn.as_ptr(),
jcn.as_ptr(),
&mut self.lkeep,
self.keep.as_mut_ptr(),
self.iwork.as_mut_ptr(),
self.icntl.as_ptr(),
self.info.as_mut_ptr(),
self.rinfo.as_mut_ptr(),
);
}
if self.info[0] < 0 {
return ESymSolverStatus::FatalError;
}
let scale = self.options.pre_alloc;
let suggested_lfact = (self.info[8] as Number * scale).ceil() as Index;
let suggested_lifact = (self.info[9] as Number * scale).ceil() as Index;
self.lfact = suggested_lfact.max(self.info[8]);
self.lifact = suggested_lifact.max(self.info[9]);
self.fact = vec![0.0; self.lfact as usize];
self.ifact = vec![0; self.lifact as usize];
ESymSolverStatus::Success
}
fn factorization(
&mut self,
check_neg_evals: bool,
number_of_neg_evals: Index,
) -> ESymSolverStatus {
self.cntl[0] = self.options.pivtol;
let n = self.dim;
let ne = self.nonzeros;
loop {
unsafe {
ma57bd_(
&n,
&ne,
self.a.as_ptr(),
self.fact.as_mut_ptr(),
&self.lfact,
self.ifact.as_mut_ptr(),
&self.lifact,
&self.lkeep,
self.keep.as_mut_ptr(),
self.iwork.as_mut_ptr(),
self.icntl.as_ptr(),
self.cntl.as_ptr(),
self.info.as_mut_ptr(),
self.rinfo.as_mut_ptr(),
);
}
self.negevals = self.info[24 - 1];
match self.info[0] {
0 => break,
-3 => self.grow_fact(),
-4 => self.grow_ifact(),
4 => return ESymSolverStatus::Singular,
v if v < 0 => return ESymSolverStatus::FatalError,
_ => return ESymSolverStatus::FatalError,
}
}
if check_neg_evals && number_of_neg_evals != self.negevals {
return ESymSolverStatus::WrongInertia;
}
ESymSolverStatus::Success
}
fn grow_fact(&mut self) {
let suggested = (self.info[16] as Number * self.options.pre_alloc).ceil() as Index;
let new_lfact = suggested.max(self.info[16]).max(self.lfact + 1);
let mut newfac: Vec<Number> = vec![0.0; new_lfact as usize];
let n = self.dim;
let ic: Index = 0;
let lfact_in = self.info[1];
let mut idmy: Index = 0;
unsafe {
ma57ed_(
&n,
&ic,
self.keep.as_mut_ptr(),
self.fact.as_mut_ptr(),
&lfact_in,
newfac.as_mut_ptr(),
&new_lfact,
self.ifact.as_mut_ptr(),
&lfact_in,
&mut idmy,
&new_lfact,
self.info.as_mut_ptr(),
);
}
self.fact = newfac;
self.lfact = new_lfact;
}
fn grow_ifact(&mut self) {
let suggested = (self.info[17] as Number * self.options.pre_alloc).ceil() as Index;
let new_lifact = suggested.max(self.info[17]).max(self.lifact + 1);
let mut newifc: Vec<Index> = vec![0; new_lifact as usize];
let n = self.dim;
let ic: Index = 1;
let lifact_in = self.info[1];
let mut ddmy: Number = 0.0;
unsafe {
ma57ed_(
&n,
&ic,
self.keep.as_mut_ptr(),
self.fact.as_mut_ptr(),
&lifact_in,
&mut ddmy,
&new_lifact,
self.ifact.as_mut_ptr(),
&lifact_in,
newifc.as_mut_ptr(),
&new_lifact,
self.info.as_mut_ptr(),
);
}
self.ifact = newifc;
self.lifact = new_lifact;
}
fn backsolve(&mut self, nrhs: Index, rhs_vals: &mut [Number]) -> ESymSolverStatus {
let n = self.dim;
let job: Index = 1;
let lrhs = n;
let lwork = n * nrhs;
let mut work: Vec<Number> = vec![0.0; lwork as usize];
unsafe {
ma57cd_(
&job,
&n,
self.fact.as_ptr(),
&self.lfact,
self.ifact.as_ptr(),
&self.lifact,
&nrhs,
rhs_vals.as_mut_ptr(),
&lrhs,
work.as_mut_ptr(),
&lwork,
self.iwork.as_mut_ptr(),
self.icntl.as_ptr(),
self.info.as_mut_ptr(),
);
}
if self.info[0] != 0 {
return ESymSolverStatus::FatalError;
}
ESymSolverStatus::Success
}
}
impl Default for Ma57SolverInterface {
fn default() -> Self {
Self::new()
}
}
impl SparseSymLinearSolverInterface for Ma57SolverInterface {
fn initialize_structure(
&mut self,
dim: Index,
nonzeros: Index,
ia: &[Index],
ja: &[Index],
) -> ESymSolverStatus {
assert_eq!(ia.len(), nonzeros as usize);
assert_eq!(ja.len(), nonzeros as usize);
self.dim = dim;
self.nonzeros = nonzeros;
self.a = vec![0.0; nonzeros as usize];
let status = self.symbolic_factorization(ia, ja);
if status == ESymSolverStatus::Success {
self.initialized = true;
}
status
}
fn values_array_mut(&mut self) -> &mut [Number] {
debug_assert!(self.initialized);
&mut self.a
}
fn multi_solve(
&mut self,
new_matrix: bool,
_ia: &[Index],
_ja: &[Index],
nrhs: Index,
rhs_vals: &mut [Number],
check_neg_evals: bool,
number_of_neg_evals: Index,
) -> ESymSolverStatus {
if self.pivtol_changed {
self.pivtol_changed = false;
if !new_matrix {
self.refactorize = true;
return ESymSolverStatus::CallAgain;
}
}
if new_matrix || self.refactorize {
let status = self.factorization(check_neg_evals, number_of_neg_evals);
if status != ESymSolverStatus::Success {
return status;
}
self.refactorize = false;
}
self.backsolve(nrhs, rhs_vals)
}
fn number_of_neg_evals(&self) -> Index {
debug_assert!(self.initialized);
self.negevals
}
fn increase_quality(&mut self) -> bool {
if self.options.pivtol == self.options.pivtolmax {
return false;
}
self.pivtol_changed = true;
self.options.pivtol = self.options.pivtolmax.min(self.options.pivtol.powf(0.75));
true
}
fn provides_inertia(&self) -> bool {
true
}
fn matrix_format(&self) -> EMatrixFormat {
EMatrixFormat::TripletFormat
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn factor_and_solve_spd_2x2() {
let mut s = Ma57SolverInterface::new();
let n: Index = 2;
let ne: Index = 3;
let irn: [Index; 3] = [1, 2, 2];
let jcn: [Index; 3] = [1, 1, 2];
assert_eq!(
s.initialize_structure(n, ne, &irn, &jcn),
ESymSolverStatus::Success
);
s.values_array_mut().copy_from_slice(&[2.0, 1.0, 3.0]);
let mut rhs = [3.0, 4.0];
assert_eq!(
s.multi_solve(true, &irn, &jcn, 1, &mut rhs, false, 0),
ESymSolverStatus::Success
);
assert!((rhs[0] - 1.0).abs() < 1e-12, "x0 = {}", rhs[0]);
assert!((rhs[1] - 1.0).abs() < 1e-12, "x1 = {}", rhs[1]);
assert_eq!(s.number_of_neg_evals(), 0);
assert!(s.provides_inertia());
assert_eq!(s.matrix_format(), EMatrixFormat::TripletFormat);
}
#[test]
fn detects_one_negative_eigenvalue() {
let mut s = Ma57SolverInterface::new();
let n: Index = 2;
let ne: Index = 3;
let irn: [Index; 3] = [1, 2, 2];
let jcn: [Index; 3] = [1, 1, 2];
assert_eq!(
s.initialize_structure(n, ne, &irn, &jcn),
ESymSolverStatus::Success
);
s.values_array_mut().copy_from_slice(&[1.0, 2.0, 1.0]);
let mut rhs = [3.0, 3.0];
assert_eq!(
s.multi_solve(true, &irn, &jcn, 1, &mut rhs, true, 1),
ESymSolverStatus::Success
);
assert_eq!(s.number_of_neg_evals(), 1);
assert!((rhs[0] - 1.0).abs() < 1e-12);
assert!((rhs[1] - 1.0).abs() < 1e-12);
}
#[test]
fn check_neg_evals_mismatch_returns_wrong_inertia() {
let mut s = Ma57SolverInterface::new();
let n: Index = 2;
let ne: Index = 3;
let irn: [Index; 3] = [1, 2, 2];
let jcn: [Index; 3] = [1, 1, 2];
assert_eq!(
s.initialize_structure(n, ne, &irn, &jcn),
ESymSolverStatus::Success
);
s.values_array_mut().copy_from_slice(&[2.0, 1.0, 3.0]); let mut rhs = [3.0, 4.0];
assert_eq!(
s.multi_solve(true, &irn, &jcn, 1, &mut rhs, true, 1),
ESymSolverStatus::WrongInertia
);
}
#[test]
fn increase_quality_then_resolve_triggers_call_again() {
let mut s = Ma57SolverInterface::new();
let n: Index = 2;
let ne: Index = 3;
let irn: [Index; 3] = [1, 2, 2];
let jcn: [Index; 3] = [1, 1, 2];
assert_eq!(
s.initialize_structure(n, ne, &irn, &jcn),
ESymSolverStatus::Success
);
s.values_array_mut().copy_from_slice(&[2.0, 1.0, 3.0]);
let mut rhs = [3.0, 4.0];
assert_eq!(
s.multi_solve(true, &irn, &jcn, 1, &mut rhs, false, 0),
ESymSolverStatus::Success
);
let pivtol_before = s.pivtol();
assert!(s.increase_quality());
assert!(s.pivtol() > pivtol_before);
let mut rhs = [3.0, 4.0];
assert_eq!(
s.multi_solve(false, &irn, &jcn, 1, &mut rhs, false, 0),
ESymSolverStatus::CallAgain
);
s.values_array_mut().copy_from_slice(&[2.0, 1.0, 3.0]);
let mut rhs = [3.0, 4.0];
assert_eq!(
s.multi_solve(false, &irn, &jcn, 1, &mut rhs, false, 0),
ESymSolverStatus::Success
);
}
#[test]
fn increase_quality_caps_at_pivtolmax() {
let mut opts = Options::defaults();
opts.pivtol = 1e-4;
opts.pivtolmax = 1e-4;
let mut s = Ma57SolverInterface::with_options(opts);
assert!(!s.increase_quality());
}
}