use std::os::raw::c_int;
use arpack_sys::{__BindgenComplex, cnaupd_c, cneupd_c, znaupd_c, zneupd_c};
use num_complex::{Complex32, Complex64};
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_c64<F>(
n: usize,
nev: usize,
which: Which,
matvec: F,
options: &Options,
) -> Result<MultiEigSolution<Complex64>, Error>
where
F: FnMut(&[Complex64], &mut [Complex64]),
{
eigenpairs_c64_impl(n, nev, which, matvec, options)
}
pub fn eigenpairs_c32<F>(
n: usize,
nev: usize,
which: Which,
matvec: F,
options: &Options,
) -> Result<MultiEigSolution<Complex32>, Error>
where
F: FnMut(&[Complex32], &mut [Complex32]),
{
eigenpairs_c32_impl(n, nev, which, matvec, options)
}
pub fn smallest_eigenpair_c64<F>(
n: usize,
matvec: F,
options: &Options,
) -> Result<EigSolution<Complex64>, Error>
where
F: FnMut(&[Complex64], &mut [Complex64]),
{
let multi = eigenpairs_c64(n, 1, Which::SmallestRealPart, matvec, options)?;
Ok(singular_from_multi(multi))
}
pub fn smallest_eigenpair_c32<F>(
n: usize,
matvec: F,
options: &Options,
) -> Result<EigSolution<Complex32>, Error>
where
F: FnMut(&[Complex32], &mut [Complex32]),
{
let multi = eigenpairs_c32(n, 1, Which::SmallestRealPart, matvec, options)?;
Ok(singular_from_multi(multi))
}
macro_rules! impl_complex_arnoldi_driver {
($fn:ident, $cty:ty, $float:ty, $aupd:path, $eupd:path, $tol:path) => {
fn $fn<F>(
n: usize,
nev: usize,
which: Which,
mut matvec: F,
options: &Options,
) -> Result<MultiEigSolution<$cty>, Error>
where
F: FnMut(&[$cty], &mut [$cty]),
{
if nev == 0 {
return Err(Error::InvalidParam("nev must be positive"));
}
if !which.accepted_by_complex_arnoldi() {
return Err(Error::InvalidParam(
"Which selector not accepted by the complex Arnoldi driver",
));
}
let nev_i32 = c_int_from_usize(nev)?;
if n < nev + 3 {
return Err(Error::InvalidParam(
"n too small for complex Arnoldi (require n >= nev + 3)",
));
}
let ncv = options
.ncv
.unwrap_or_else(|| (2 * nev + 4).min(n - 1).max(nev + 2));
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 !(ncv_i32 >= nev_i32 + 2 && ncv_i32 < n_i32) {
return Err(Error::InvalidParam("require nev + 2 <= ncv and 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 ncv_sq = ncv
.checked_mul(ncv)
.ok_or(Error::InvalidParam("ncv * ncv overflows usize"))?;
let three_ncv_sq = ncv_sq
.checked_mul(3)
.ok_or(Error::InvalidParam("3 * ncv^2 overflows usize"))?;
let five_ncv = ncv
.checked_mul(5)
.ok_or(Error::InvalidParam("5 * ncv overflows usize"))?;
let lworkl = three_ncv_sq
.checked_add(five_ncv)
.ok_or(Error::InvalidParam("3*ncv^2 + 5*ncv overflows usize"))?;
let workev_len = ncv
.checked_mul(2)
.ok_or(Error::InvalidParam("2 * ncv overflows usize"))?;
let lworkl_i32 = c_int_from_usize(lworkl)?;
let tol = $tol(options.tol);
let zero = <$cty>::new(0.0, 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; 14];
let mut workd = vec![zero; workd_len];
let mut workl = vec![zero; lworkl];
let mut rwork: Vec<$float> = vec![0.0; ncv];
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() as *mut __BindgenComplex<$float>,
ncv_i32,
v.as_mut_ptr() as *mut __BindgenComplex<$float>,
ldv,
iparam.as_mut_ptr(),
ipntr.as_mut_ptr(),
workd.as_mut_ptr() as *mut __BindgenComplex<$float>,
workl.as_mut_ptr() as *mut __BindgenComplex<$float>,
lworkl_i32,
rwork.as_mut_ptr(),
&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 = __BindgenComplex::<$float> { re: 0.0, im: 0.0 };
let mut workev = vec![zero; workev_len];
let mut info_eup: c_int = 0;
unsafe {
$eupd(
rvec,
howmny,
select.as_mut_ptr(),
d.as_mut_ptr() as *mut __BindgenComplex<$float>,
v.as_mut_ptr() as *mut __BindgenComplex<$float>,
ldv,
sigma,
workev.as_mut_ptr() as *mut __BindgenComplex<$float>,
bmat,
n_i32,
which_ptr,
nev_i32,
tol,
resid.as_mut_ptr() as *mut __BindgenComplex<$float>,
ncv_i32,
v.as_mut_ptr() as *mut __BindgenComplex<$float>,
ldv,
iparam.as_mut_ptr(),
ipntr.as_mut_ptr(),
workd.as_mut_ptr() as *mut __BindgenComplex<$float>,
workl.as_mut_ptr() as *mut __BindgenComplex<$float>,
lworkl_i32,
rwork.as_mut_ptr(),
&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_complex_arnoldi_driver!(
eigenpairs_c64_impl,
Complex64,
f64,
znaupd_c,
zneupd_c,
tol_as_f64
);
impl_complex_arnoldi_driver!(
eigenpairs_c32_impl,
Complex32,
f32,
cnaupd_c,
cneupd_c,
tol_as_f32
);