use num_complex::Complex;
use super::arnoldi::{ArnoldiError, ArnoldiOptions, ArnoldiResult, RitzPair};
#[allow(non_camel_case_types)]
type a_int = i32;
unsafe extern "C" {
#[allow(clippy::too_many_arguments)]
fn znaupd_c(
ido: *mut a_int,
bmat: *const std::ffi::c_char,
n: a_int,
which: *const std::ffi::c_char,
nev: a_int,
tol: f64,
resid: *mut Complex<f64>,
ncv: a_int,
v: *mut Complex<f64>,
ldv: a_int,
iparam: *mut a_int,
ipntr: *mut a_int,
workd: *mut Complex<f64>,
workl: *mut Complex<f64>,
lworkl: a_int,
rwork: *mut f64,
info: *mut a_int,
);
#[allow(clippy::too_many_arguments)]
fn zneupd_c(
rvec: a_int,
howmny: *const std::ffi::c_char,
select: *const a_int,
d: *mut Complex<f64>,
z: *mut Complex<f64>,
ldz: a_int,
sigma: Complex<f64>,
workev: *mut Complex<f64>,
bmat: *const std::ffi::c_char,
n: a_int,
which: *const std::ffi::c_char,
nev: a_int,
tol: f64,
resid: *mut Complex<f64>,
ncv: a_int,
v: *mut Complex<f64>,
ldv: a_int,
iparam: *mut a_int,
ipntr: *mut a_int,
workd: *mut Complex<f64>,
workl: *mut Complex<f64>,
lworkl: a_int,
rwork: *mut f64,
info: *mut a_int,
);
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ArpackError {
Iteration(i32),
Extraction(i32),
OperatorFailed,
NotConverged { wanted: usize, converged: usize },
Sizes,
}
impl std::fmt::Display for ArpackError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArpackError::Iteration(info) => write!(f, "znaupd failed with info = {info}"),
ArpackError::Extraction(info) => write!(f, "zneupd failed with info = {info}"),
ArpackError::OperatorFailed => write!(f, "the operator could not be applied"),
ArpackError::NotConverged { wanted, converged } => {
write!(f, "ARPACK converged {converged} of {wanted} eigenvalues")
}
ArpackError::Sizes => write!(f, "nothing to iterate on"),
}
}
}
impl std::error::Error for ArpackError {}
pub fn eigenpairs<F>(
n: usize,
apply: F,
opts: &ArnoldiOptions,
) -> Result<ArnoldiResult, ArpackError>
where
F: Fn(&[Complex<f64>]) -> Option<Vec<Complex<f64>>>,
{
if n < 2 || opts.count == 0 || opts.count >= n {
return Err(ArpackError::Sizes);
}
let nev = opts.count;
let requested = if opts.max_dim == 0 {
(2 * nev + 20).max(30)
} else {
opts.max_dim
};
let ncv = requested.clamp(nev + 2, n);
let (ni, nevi, ncvi) = (n as a_int, nev as a_int, ncv as a_int);
let lworkl = 3 * ncv * ncv + 5 * ncv;
let zero = Complex::new(0.0, 0.0);
let mut resid = vec![zero; n];
let mut v = vec![zero; n * ncv];
let mut workd = vec![zero; 3 * n];
let mut workl = vec![zero; lworkl];
let mut rwork = vec![0.0f64; ncv];
let mut iparam = [0 as a_int; 11];
let mut ipntr = [0 as a_int; 14];
iparam[0] = 1; iparam[2] = ((opts.max_restarts + 1) * 300).max(300) as a_int; iparam[6] = 1;
let bmat = c"I";
let which = c"LM"; let howmny = c"A";
let mut ido: a_int = 0;
let mut info: a_int = 0;
loop {
unsafe {
znaupd_c(
&mut ido,
bmat.as_ptr(),
ni,
which.as_ptr(),
nevi,
opts.tol,
resid.as_mut_ptr(),
ncvi,
v.as_mut_ptr(),
ni,
iparam.as_mut_ptr(),
ipntr.as_mut_ptr(),
workd.as_mut_ptr(),
workl.as_mut_ptr(),
lworkl as a_int,
rwork.as_mut_ptr(),
&mut info,
);
}
match ido {
-1 | 1 => {
let from = (ipntr[0] - 1) as usize;
let to = (ipntr[1] - 1) as usize;
let x: Vec<Complex<f64>> = workd[from..from + n].to_vec();
let y = apply(&x).ok_or(ArpackError::OperatorFailed)?;
workd[to..to + n].copy_from_slice(&y);
}
_ => break,
}
}
if info < 0 {
return Err(ArpackError::Iteration(info));
}
let converged = iparam[4] as usize;
if converged < nev {
return Err(ArpackError::NotConverged {
wanted: nev,
converged,
});
}
let mut d = vec![zero; nev + 1];
let mut z = vec![zero; n * nev];
let mut workev = vec![zero; 2 * ncv];
let select = vec![0 as a_int; ncv];
let mut info_eupd: a_int = 0;
unsafe {
zneupd_c(
1, howmny.as_ptr(),
select.as_ptr(),
d.as_mut_ptr(),
z.as_mut_ptr(),
ni,
zero, workev.as_mut_ptr(),
bmat.as_ptr(),
ni,
which.as_ptr(),
nevi,
opts.tol,
resid.as_mut_ptr(),
ncvi,
v.as_mut_ptr(),
ni,
iparam.as_mut_ptr(),
ipntr.as_mut_ptr(),
workd.as_mut_ptr(),
workl.as_mut_ptr(),
lworkl as a_int,
rwork.as_mut_ptr(),
&mut info_eupd,
);
}
if info_eupd < 0 {
return Err(ArpackError::Extraction(info_eupd));
}
let mut pairs = Vec::with_capacity(nev);
for i in 0..nev {
let theta = d[i];
let vector: Vec<Complex<f64>> = z[i * n..(i + 1) * n].to_vec();
let image = apply(&vector).ok_or(ArpackError::OperatorFailed)?;
let raw = image
.iter()
.zip(&vector)
.map(|(a, b)| (a - theta * b).norm_sqr())
.sum::<f64>()
.sqrt();
let magnitude = theta.norm();
pairs.push(RitzPair {
theta,
vector,
residual: if magnitude > 0.0 {
raw / (magnitude * magnitude)
} else {
f64::INFINITY
},
converged: magnitude > 0.0 && raw <= opts.tol.max(1e-14) * magnitude,
});
}
pairs.sort_by(|a, b| {
b.theta
.norm()
.partial_cmp(&a.theta.norm())
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(ArnoldiResult {
pairs,
restarts: 0,
dim: ncv,
})
}
impl From<ArpackError> for ArnoldiError {
fn from(e: ArpackError) -> Self {
match e {
ArpackError::OperatorFailed => ArnoldiError::OperatorFailed,
ArpackError::Sizes => ArnoldiError::Empty,
_ => ArnoldiError::Eigen,
}
}
}