use std::os::raw::c_int;
use arpack_sys::{dsaupd_c, dseupd_c, ssaupd_c, sseupd_c};
use crate::error::{Error, aupd_error, eupd_error};
use crate::lock::lock;
use crate::solution::{
EigSolution, MultiEigSolution, c_int_from_usize, singular_from_multi, tol_as_f32, tol_as_f64,
usize_from_iparam,
};
use crate::which::Which;
#[derive(Clone, Debug)]
pub struct Options {
pub tol: f64,
pub max_iter: usize,
pub ncv: Option<usize>,
}
impl Default for Options {
fn default() -> Self {
Self {
tol: 0.0,
max_iter: 300,
ncv: None,
}
}
}
pub fn eigenpairs_f64<F>(
n: usize,
nev: usize,
which: Which,
matvec: F,
options: &Options,
) -> Result<MultiEigSolution<f64>, Error>
where
F: FnMut(&[f64], &mut [f64]),
{
eigenpairs_f64_impl(n, nev, which, matvec, options)
}
pub fn eigenpairs_f32<F>(
n: usize,
nev: usize,
which: Which,
matvec: F,
options: &Options,
) -> Result<MultiEigSolution<f32>, Error>
where
F: FnMut(&[f32], &mut [f32]),
{
eigenpairs_f32_impl(n, nev, which, matvec, options)
}
pub fn smallest_eigenpair_f64<F>(
n: usize,
matvec: F,
options: &Options,
) -> Result<EigSolution<f64>, Error>
where
F: FnMut(&[f64], &mut [f64]),
{
let multi = eigenpairs_f64(n, 1, Which::SmallestAlgebraic, matvec, options)?;
Ok(singular_from_multi(multi))
}
pub fn smallest_eigenpair_f32<F>(
n: usize,
matvec: F,
options: &Options,
) -> Result<EigSolution<f32>, Error>
where
F: FnMut(&[f32], &mut [f32]),
{
let multi = eigenpairs_f32(n, 1, Which::SmallestAlgebraic, matvec, options)?;
Ok(singular_from_multi(multi))
}
macro_rules! impl_real_sym_driver {
($fn:ident, $ty:ty, $aupd:path, $eupd:path, $tol:path) => {
fn $fn<F>(
n: usize,
nev: usize,
which: Which,
mut matvec: F,
options: &Options,
) -> Result<MultiEigSolution<$ty>, Error>
where
F: FnMut(&[$ty], &mut [$ty]),
{
if nev == 0 {
return Err(Error::InvalidParam("nev must be positive"));
}
if !which.accepted_by_symmetric() {
return Err(Error::InvalidParam(
"Which selector not accepted by the real-symmetric driver",
));
}
let nev_i32 = c_int_from_usize(nev)?;
if n < nev + 2 {
return Err(Error::InvalidParam(
"n too small for ARPACK (require n >= nev + 2)",
));
}
let ncv = options
.ncv
.unwrap_or_else(|| (2 * nev + 4).min(n - 1).max(nev + 1));
let n_i32 = c_int_from_usize(n)?;
let ncv_i32 = c_int_from_usize(ncv)?;
let max_iter_i32 = c_int_from_usize(options.max_iter)?;
if !(nev_i32 < ncv_i32 && ncv_i32 < n_i32) {
return Err(Error::InvalidParam("require nev < ncv < n"));
}
if max_iter_i32 <= 0 {
return Err(Error::InvalidParam("max_iter must be positive"));
}
let v_len = n
.checked_mul(ncv)
.ok_or(Error::InvalidParam("n * ncv overflows usize"))?;
let workd_len = n
.checked_mul(3)
.ok_or(Error::InvalidParam("3 * n overflows usize"))?;
let lworkl = ncv
.checked_add(8)
.and_then(|s| ncv.checked_mul(s))
.ok_or(Error::InvalidParam("ncv * (ncv + 8) overflows usize"))?;
let lworkl_i32 = c_int_from_usize(lworkl)?;
let tol = $tol(options.tol);
let zero: $ty = 0.0;
let mut resid = vec![zero; n];
let mut v = vec![zero; v_len];
let ldv = n_i32;
let mut iparam = [0i32; 11];
iparam[0] = 1; iparam[2] = max_iter_i32;
iparam[3] = 1; iparam[6] = 1; let mut ipntr = [0i32; 11];
let mut workd = vec![zero; workd_len];
let mut workl = vec![zero; lworkl];
let bmat = c"I".as_ptr();
let which_ptr = which.as_c_str().as_ptr();
let _guard = lock();
let mut ido: c_int = 0;
let mut info: c_int = 0;
let mut x_buf = vec![zero; n];
loop {
unsafe {
$aupd(
&mut ido,
bmat,
n_i32,
which_ptr,
nev_i32,
tol,
resid.as_mut_ptr(),
ncv_i32,
v.as_mut_ptr(),
ldv,
iparam.as_mut_ptr(),
ipntr.as_mut_ptr(),
workd.as_mut_ptr(),
workl.as_mut_ptr(),
lworkl_i32,
&mut info,
);
}
match ido {
-1 | 1 => {
let x_off = (ipntr[0] - 1) as usize;
let y_off = (ipntr[1] - 1) as usize;
debug_assert!(x_off + n <= workd.len() && y_off + n <= workd.len());
x_buf.copy_from_slice(&workd[x_off..x_off + n]);
matvec(&x_buf, &mut workd[y_off..y_off + n]);
}
99 => break,
other => return Err(Error::UnexpectedIdo(other)),
}
}
let nconv = usize_from_iparam(iparam[4]);
let iters = usize_from_iparam(iparam[2]);
let n_matvec = usize_from_iparam(iparam[8]);
if let Some(err) = aupd_error(info, iters, nconv, n_matvec) {
return Err(err);
}
let rvec: c_int = 1;
let howmny = c"A".as_ptr();
let mut select = vec![0i32; ncv];
let mut d = vec![zero; nev];
let sigma: $ty = 0.0;
let mut info_eup: c_int = 0;
unsafe {
$eupd(
rvec,
howmny,
select.as_mut_ptr(),
d.as_mut_ptr(),
v.as_mut_ptr(),
ldv,
sigma,
bmat,
n_i32,
which_ptr,
nev_i32,
tol,
resid.as_mut_ptr(),
ncv_i32,
v.as_mut_ptr(),
ldv,
iparam.as_mut_ptr(),
ipntr.as_mut_ptr(),
workd.as_mut_ptr(),
workl.as_mut_ptr(),
lworkl_i32,
&mut info_eup,
);
}
if let Some(err) = eupd_error(info_eup, iters, nconv, n_matvec) {
return Err(err);
}
let extracted = nconv.min(nev);
let eigenvalues = d[..extracted].to_vec();
let mut eigenvectors = Vec::with_capacity(extracted);
for k in 0..extracted {
eigenvectors.push(v[k * n..(k + 1) * n].to_vec());
}
Ok(MultiEigSolution {
eigenvalues,
eigenvectors,
nev_requested: nev,
nconv,
iters,
n_matvec,
})
}
};
}
impl_real_sym_driver!(eigenpairs_f64_impl, f64, dsaupd_c, dseupd_c, tol_as_f64);
impl_real_sym_driver!(eigenpairs_f32_impl, f32, ssaupd_c, sseupd_c, tol_as_f32);