use std::ffi::c_void;
#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code, clippy::all)]
mod bindings {
include!(concat!(env!("OUT_DIR"), "/pardiso_bindings.rs"));
}
use bindings::{pardiso, pardisoinit};
const MTYPE_REAL_NONSYMMETRIC: i32 = 11;
const PHASE_ANALYSIS: i32 = 11;
const PHASE_NUMERICAL_FACTORIZATION: i32 = 22;
const PHASE_SOLVE: i32 = 33;
const IPARM_NUM_PERTURBED_PIVOTS: usize = 13;
const PHASE_RELEASE_MEMORY: i32 = -1;
fn build_csr_structure(n: usize, pairs: &[(usize, usize)]) -> (Vec<i32>, Vec<i32>, Vec<Vec<usize>>) {
let mut order: Vec<usize> = (0..pairs.len()).collect();
order.sort_by_key(|&i| (pairs[i].0, pairs[i].1));
let mut row_ptr = vec![0i32; n + 1];
let mut col_idx: Vec<i32> = Vec::new();
let mut groups: Vec<Vec<usize>> = Vec::new();
let mut idx = 0;
while idx < order.len() {
let (row, col) = pairs[order[idx]];
let mut group = vec![order[idx]];
let mut k = idx + 1;
while k < order.len() && pairs[order[k]] == (row, col) {
group.push(order[k]);
k += 1;
}
col_idx.push(col as i32);
groups.push(group);
row_ptr[row + 1] += 1;
idx = k;
}
for r in 0..n {
row_ptr[r + 1] += row_ptr[r];
}
(row_ptr, col_idx, groups)
}
fn pack_real_values(entries: &[(usize, usize, f64)], groups: &[Vec<usize>]) -> Vec<f64> {
groups.iter().map(|g| g.iter().map(|&i| entries[i].2).sum()).collect()
}
fn pack_real_values_slice(values: &[f64], groups: &[Vec<usize>]) -> Vec<f64> {
groups.iter().map(|g| g.iter().map(|&i| values[i]).sum()).collect()
}
pub struct PardisoRealSystem {
n: usize,
row_ptr: Vec<i32>,
col_idx: Vec<i32>,
groups: Vec<Vec<usize>>,
pt: [*mut c_void; 64],
iparm: [i32; 64],
}
impl PardisoRealSystem {
pub fn new(n: usize, entries: &[(usize, usize, f64)]) -> Option<Self> {
let pairs: Vec<(usize, usize)> = entries.iter().map(|&(r, c, _)| (r, c)).collect();
let (row_ptr, col_idx, groups) = build_csr_structure(n, &pairs);
let mut values = pack_real_values(entries, &groups);
let mut pt: [*mut c_void; 64] = [std::ptr::null_mut(); 64];
let mtype = MTYPE_REAL_NONSYMMETRIC;
let mut iparm: [i32; 64] = [0; 64];
unsafe { pardisoinit(pt.as_mut_ptr() as *mut c_void, &mtype, iparm.as_mut_ptr()) };
iparm[0] = 1; iparm[34] = 1;
let mut system = Self { n, row_ptr, col_idx, groups, pt, iparm };
let a = values.as_mut_ptr() as *const c_void;
if system.call(PHASE_ANALYSIS, a, std::ptr::null_mut(), std::ptr::null_mut()) != 0 {
return None;
}
if system.call(PHASE_NUMERICAL_FACTORIZATION, a, std::ptr::null_mut(), std::ptr::null_mut()) != 0
|| system.iparm[IPARM_NUM_PERTURBED_PIVOTS] != 0
{
return None;
}
Some(system)
}
pub fn factor_and_solve(&mut self, entries: &[(usize, usize, f64)], rhs: &[f64]) -> Option<Vec<f64>> {
let values = pack_real_values(entries, &self.groups);
self.solve_packed(values, rhs)
}
fn solve_packed(&mut self, mut values: Vec<f64>, rhs: &[f64]) -> Option<Vec<f64>> {
let a = values.as_mut_ptr() as *const c_void;
if self.call(PHASE_NUMERICAL_FACTORIZATION, a, std::ptr::null_mut(), std::ptr::null_mut()) != 0
|| self.iparm[IPARM_NUM_PERTURBED_PIVOTS] != 0
{
return None;
}
let mut b = rhs.to_vec();
let mut x: Vec<f64> = vec![0.0; self.n];
let error =
self.call(PHASE_SOLVE, a, b.as_mut_ptr() as *mut c_void, x.as_mut_ptr() as *mut c_void);
if error != 0 || x.iter().any(|v| !v.is_finite()) {
return None;
}
Some(x)
}
fn call(&mut self, phase: i32, a: *const c_void, b: *mut c_void, x: *mut c_void) -> i32 {
let maxfct: i32 = 1;
let mnum: i32 = 1;
let mtype: i32 = MTYPE_REAL_NONSYMMETRIC;
let n = self.n as i32;
let nrhs: i32 = 1;
let msglvl: i32 = 0;
let mut error: i32 = 0;
let perm: *mut i32 = std::ptr::null_mut();
unsafe {
pardiso(
self.pt.as_mut_ptr() as *mut c_void,
&maxfct,
&mnum,
&mtype,
&phase,
&n,
a,
self.row_ptr.as_ptr(),
self.col_idx.as_ptr(),
perm,
&nrhs,
self.iparm.as_mut_ptr(),
&msglvl,
b,
x,
&mut error,
);
}
error
}
}
impl crate::solver::LinearSolver for PardisoRealSystem {
fn new(n: usize, entries: &[(usize, usize, f64)]) -> Option<Self> {
PardisoRealSystem::new(n, entries)
}
fn factor_and_solve(&mut self, entries: &[(usize, usize, f64)], rhs: &[f64]) -> Option<Vec<f64>> {
PardisoRealSystem::factor_and_solve(self, entries, rhs)
}
fn factor_and_solve_values(&mut self, values: &[f64], rhs: &[f64]) -> Option<Vec<f64>> {
self.solve_packed(pack_real_values_slice(values, &self.groups), rhs)
}
}
impl Drop for PardisoRealSystem {
fn drop(&mut self) {
self.call(PHASE_RELEASE_MEMORY, std::ptr::null(), std::ptr::null_mut(), std::ptr::null_mut());
}
}
#[cfg(test)]
mod ffi_smoke_test {
use super::bindings::{pardiso, pardisoinit};
use std::ffi::c_void;
#[test]
fn solves_simple_system_via_raw_ffi() {
let n: i32 = 2;
let ia: [i32; 3] = [0, 2, 4];
let ja: [i32; 4] = [0, 1, 0, 1];
let a: [f64; 4] = [2.0, 1.0, 1.0, 3.0];
let mut b: [f64; 2] = [5.0, 10.0];
let mut x: [f64; 2] = [0.0, 0.0];
let mut pt: [*mut c_void; 64] = [std::ptr::null_mut(); 64];
let mtype: i32 = 11; let mut iparm: [i32; 64] = [0; 64];
unsafe { pardisoinit(pt.as_mut_ptr() as *mut c_void, &mtype, iparm.as_mut_ptr()) };
iparm[0] = 1; iparm[34] = 1;
let maxfct: i32 = 1;
let mnum: i32 = 1;
let nrhs: i32 = 1;
let msglvl: i32 = 0;
let mut error: i32 = 0;
let perm: *mut i32 = std::ptr::null_mut();
for phase in [11, 22, 33] {
unsafe {
pardiso(
pt.as_mut_ptr() as *mut c_void,
&maxfct,
&mnum,
&mtype,
&phase,
&n,
a.as_ptr() as *const c_void,
ia.as_ptr(),
ja.as_ptr(),
perm,
&nrhs,
iparm.as_mut_ptr(),
&msglvl,
b.as_mut_ptr() as *mut c_void,
x.as_mut_ptr() as *mut c_void,
&mut error,
);
}
assert_eq!(error, 0, "PARDISO phase {phase} failed with error {error}");
}
let release_phase: i32 = -1;
unsafe {
pardiso(
pt.as_mut_ptr() as *mut c_void,
&maxfct,
&mnum,
&mtype,
&release_phase,
&n,
std::ptr::null(),
ia.as_ptr(),
ja.as_ptr(),
perm,
&nrhs,
iparm.as_mut_ptr(),
&msglvl,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut error,
);
}
assert_eq!(error, 0, "PARDISO release phase failed with error {error}");
assert!((x[0] - 1.0).abs() < 1e-10);
assert!((x[1] - 3.0).abs() < 1e-10);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn solve_simple_system() {
let entries = vec![(0, 0, 2.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0)];
let mut sys = PardisoRealSystem::new(2, &entries).unwrap();
let x = sys.factor_and_solve(&entries, &[5.0, 10.0]).unwrap();
assert!((x[0] - 1.0).abs() < 1e-10);
assert!((x[1] - 3.0).abs() < 1e-10);
}
#[test]
fn duplicate_entries_sum() {
let entries = vec![
(0, 0, 1.0),
(0, 0, 1.0),
(0, 1, 1.0),
(1, 0, 1.0),
(1, 1, 3.0),
];
let mut sys = PardisoRealSystem::new(2, &entries).unwrap();
let x = sys.factor_and_solve(&entries, &[5.0, 10.0]).unwrap();
assert!((x[0] - 1.0).abs() < 1e-10);
assert!((x[1] - 3.0).abs() < 1e-10);
}
#[test]
fn refactor_reuses_symbolic() {
let entries_a = vec![(0, 0, 2.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 3.0)];
let mut sys = PardisoRealSystem::new(2, &entries_a).unwrap();
let x = sys.factor_and_solve(&entries_a, &[5.0, 10.0]).unwrap();
assert!((x[0] - 1.0).abs() < 1e-10);
assert!((x[1] - 3.0).abs() < 1e-10);
let entries_b = vec![(0, 0, 4.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 2.0)];
let x2 = sys.factor_and_solve(&entries_b, &[6.0, 5.0]).unwrap();
assert!((x2[0] - 1.0).abs() < 1e-10);
assert!((x2[1] - 2.0).abs() < 1e-10);
}
#[test]
fn singular_matrix_returns_none() {
let entries = vec![(0, 0, 1.0), (0, 1, 1.0), (1, 0, 2.0), (1, 1, 2.0)];
let sys = PardisoRealSystem::new(2, &entries);
if let Some(mut sys) = sys {
assert!(sys.factor_and_solve(&entries, &[1.0, 2.0]).is_none());
}
}
}