#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::coupled_iteration::ConfigFault;
use crate::math::Fix128;
mod bicgstab;
mod equilibrate;
mod gmres;
mod precond;
pub use bicgstab::bicgstab;
pub use equilibrate::{solve_equilibrated, BlockEquilibration, BlockScale, KrylovMethod};
pub use gmres::gmres;
pub use precond::{BlockJacobiPreconditioner, IdentityPreconditioner, JacobiPreconditioner};
pub const BREAKDOWN_RELATIVE: Fix128 = Fix128::from_raw(0, 1 << 24);
const STAGNATION_MIN_IMPROVEMENT: Fix128 = Fix128::from_raw(0, 1 << 54);
pub trait LinearOperator {
fn dim(&self) -> usize;
fn apply(&self, x: &[Fix128], y: &mut [Fix128]);
}
pub trait Preconditioner {
fn dim(&self) -> usize;
fn apply(&self, r: &[Fix128], z: &mut [Fix128]);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DenseMatrix {
dim: usize,
entries: Vec<Fix128>,
}
impl DenseMatrix {
pub fn try_new(dim: usize, entries: Vec<Fix128>) -> Result<Self, LinearSolverError> {
let expected = dim
.checked_mul(dim)
.ok_or(LinearSolverError::DimensionMismatch {
expected: usize::MAX,
found: entries.len(),
})?;
if entries.len() != expected {
return Err(LinearSolverError::DimensionMismatch {
expected,
found: entries.len(),
});
}
Ok(Self { dim, entries })
}
#[must_use]
pub fn get(&self, row: usize, col: usize) -> Fix128 {
assert!(
row < self.dim && col < self.dim,
"DenseMatrix index out of range"
);
self.entries[row * self.dim + col]
}
#[must_use]
pub fn dim(&self) -> usize {
self.dim
}
}
impl LinearOperator for DenseMatrix {
fn dim(&self) -> usize {
self.dim
}
fn apply(&self, x: &[Fix128], y: &mut [Fix128]) {
for (row, out) in y.iter_mut().enumerate().take(self.dim) {
let start = row * self.dim;
let mut sum = Fix128::ZERO;
for (a, &xv) in self.entries[start..start + self.dim].iter().zip(x) {
sum = sum + *a * xv;
}
*out = sum;
}
}
}
pub struct FnOperator<F> {
dim: usize,
f: F,
}
impl<F: Fn(&[Fix128], &mut [Fix128])> FnOperator<F> {
pub fn new(dim: usize, f: F) -> Self {
Self { dim, f }
}
}
impl<F: Fn(&[Fix128], &mut [Fix128])> LinearOperator for FnOperator<F> {
fn dim(&self) -> usize {
self.dim
}
fn apply(&self, x: &[Fix128], y: &mut [Fix128]) {
(self.f)(x, y);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KrylovConfig {
max_iterations: u32,
relative_tolerance: Fix128,
absolute_tolerance: Fix128,
restart: usize,
stagnation_window: u32,
}
impl KrylovConfig {
pub const DEFAULT_RESTART: usize = 30;
pub const DEFAULT_STAGNATION_WINDOW: u32 = 32;
pub fn try_new(
max_iterations: u32,
relative_tolerance: Fix128,
) -> Result<Self, LinearSolverError> {
if max_iterations == 0 {
return Err(LinearSolverError::InvalidConfig(
KrylovConfigFault::ZeroIterationBudget,
));
}
if relative_tolerance <= Fix128::ZERO {
return Err(LinearSolverError::InvalidConfig(
KrylovConfigFault::NonPositiveTolerance,
));
}
Ok(Self {
max_iterations,
relative_tolerance,
absolute_tolerance: Fix128::ZERO,
restart: Self::DEFAULT_RESTART,
stagnation_window: Self::DEFAULT_STAGNATION_WINDOW,
})
}
pub fn with_absolute_tolerance(
self,
absolute_tolerance: Fix128,
) -> Result<Self, LinearSolverError> {
if absolute_tolerance.is_negative() {
return Err(LinearSolverError::InvalidConfig(
KrylovConfigFault::NegativeAbsoluteTolerance,
));
}
Ok(Self {
absolute_tolerance,
..self
})
}
pub fn with_restart(self, restart: usize) -> Result<Self, LinearSolverError> {
if restart == 0 {
return Err(LinearSolverError::InvalidConfig(
KrylovConfigFault::ZeroRestart,
));
}
Ok(Self { restart, ..self })
}
pub fn with_stagnation_window(self, window: u32) -> Result<Self, LinearSolverError> {
if window == 0 {
return Err(LinearSolverError::InvalidConfig(
KrylovConfigFault::ZeroStagnationWindow,
));
}
Ok(Self {
stagnation_window: window,
..self
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum KrylovConfigFault {
ZeroIterationBudget,
NonPositiveTolerance,
NegativeAbsoluteTolerance,
ZeroRestart,
ZeroStagnationWindow,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BreakdownKind {
SingularHessenberg,
ShadowResidualOrthogonal,
DirectionOrthogonal,
StabilizerVanished,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum LinearSolverError {
DimensionMismatch {
expected: usize,
found: usize,
},
InvalidConfig(KrylovConfigFault),
NotConverged {
iterations: u32,
relative_residual: Fix128,
},
Stagnated {
iterations: u32,
relative_residual: Fix128,
without_improvement: u32,
},
Breakdown {
iterations: u32,
kind: BreakdownKind,
relative_residual: Fix128,
},
ArithmeticOverflow,
SingularPreconditioner {
index: usize,
},
BlockBelowProductFloor {
block: usize,
relative_magnitude: Fix128,
},
Equilibration(ConfigFault),
InvalidBlockLayout,
}
impl core::fmt::Display for LinearSolverError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KrylovStats {
pub iterations: u32,
pub restarts: u32,
pub residual_history: Vec<Fix128>,
pub residual_norm: Fix128,
pub rhs_norm: Fix128,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KrylovSolution {
pub x: Vec<Fix128>,
pub stats: KrylovStats,
}
fn to_raw(x: Fix128) -> i128 {
(i128::from(x.hi) << 64) | i128::from(x.lo)
}
fn from_raw_i128(raw: i128) -> Fix128 {
Fix128::from_raw((raw >> 64) as i64, raw as u64)
}
fn pow2_exponent(x: Fix128) -> Option<i32> {
let raw = to_raw(x);
if raw == 0 {
return None;
}
let magnitude = raw.unsigned_abs();
let bits = 128 - magnitude.leading_zeros() as i32;
Some(bits - 64)
}
fn shift(x: Fix128, k: i32) -> Result<Fix128, LinearSolverError> {
let raw = to_raw(x);
if k >= 0 {
if raw == 0 {
return Ok(x);
}
let k = k.unsigned_abs();
if k >= 127 || raw.unsigned_abs().leading_zeros() <= k {
return Err(LinearSolverError::ArithmeticOverflow);
}
Ok(from_raw_i128(raw << k))
} else {
let k = k.unsigned_abs();
if k >= 127 {
return Ok(from_raw_i128(raw >> 127));
}
Ok(from_raw_i128(raw >> k))
}
}
fn max_abs(v: &[Fix128]) -> Fix128 {
let mut worst = Fix128::ZERO;
for &x in v {
let a = x.abs();
if a > worst {
worst = a;
}
}
worst
}
#[derive(Clone, Copy, Debug)]
struct Scaled {
mantissa: Fix128,
exponent: i32,
}
impl Scaled {
const ZERO: Self = Self {
mantissa: Fix128::ZERO,
exponent: 0,
};
fn value(self) -> Result<Fix128, LinearSolverError> {
shift(self.mantissa, self.exponent)
}
fn ratio(self, other: Self) -> Result<Fix128, LinearSolverError> {
shift(
self.mantissa / other.mantissa,
self.exponent - other.exponent,
)
}
}
fn dot_scaled(a: &[Fix128], b: &[Fix128]) -> Result<Scaled, LinearSolverError> {
let (Some(ea), Some(eb)) = (pow2_exponent(max_abs(a)), pow2_exponent(max_abs(b))) else {
return Ok(Scaled::ZERO);
};
let mut sum = Fix128::ZERO;
for (&x, &y) in a.iter().zip(b) {
sum = sum + shift(x, -ea)? * shift(y, -eb)?;
}
Ok(Scaled {
mantissa: sum,
exponent: ea + eb,
})
}
fn norm_scaled(v: &[Fix128]) -> Result<Scaled, LinearSolverError> {
let Some(e) = pow2_exponent(max_abs(v)) else {
return Ok(Scaled::ZERO);
};
let mut sum = Fix128::ZERO;
for &x in v {
let s = shift(x, -e)?;
sum = sum + s * s;
}
Ok(Scaled {
mantissa: sum.sqrt(),
exponent: e,
})
}
fn dot(a: &[Fix128], b: &[Fix128]) -> Result<Fix128, LinearSolverError> {
dot_scaled(a, b)?.value()
}
fn norm(v: &[Fix128]) -> Result<Fix128, LinearSolverError> {
norm_scaled(v)?.value()
}
fn cosine_vanishes(inner: Scaled, norm_a: Scaled, norm_b: Scaled) -> bool {
if norm_a.mantissa.is_zero() || norm_b.mantissa.is_zero() {
return true;
}
inner.mantissa.abs() <= BREAKDOWN_RELATIVE * norm_a.mantissa * norm_b.mantissa
}
fn hypot(a: Fix128, b: Fix128) -> Result<Fix128, LinearSolverError> {
norm(&[a, b])
}
fn residual<A: LinearOperator + ?Sized>(op: &A, x: &[Fix128], b: &[Fix128], r: &mut [Fix128]) {
op.apply(x, r);
for (ri, &bi) in r.iter_mut().zip(b) {
*ri = bi - *ri;
}
}
fn relative(residual_norm: Fix128, rhs_norm: Fix128) -> Fix128 {
if rhs_norm.is_zero() {
Fix128::ZERO
} else {
residual_norm / rhs_norm
}
}
enum Start {
Trivial(KrylovSolution),
Run { rhs_norm: Fix128, target: Fix128 },
}
fn start<A: LinearOperator + ?Sized, M: Preconditioner + ?Sized>(
op: &A,
precond: &M,
b: &[Fix128],
config: &KrylovConfig,
) -> Result<Start, LinearSolverError> {
let n = op.dim();
if b.len() != n {
return Err(LinearSolverError::DimensionMismatch {
expected: n,
found: b.len(),
});
}
if precond.dim() != n {
return Err(LinearSolverError::DimensionMismatch {
expected: n,
found: precond.dim(),
});
}
let rhs_norm = norm(b)?;
if rhs_norm.is_zero() {
return Ok(Start::Trivial(KrylovSolution {
x: vec![Fix128::ZERO; n],
stats: KrylovStats {
iterations: 0,
restarts: 0,
residual_history: vec![Fix128::ZERO],
residual_norm: Fix128::ZERO,
rhs_norm,
},
}));
}
let requested = config.relative_tolerance * rhs_norm;
let target = if requested > config.absolute_tolerance {
requested
} else {
config.absolute_tolerance
};
Ok(Start::Run { rhs_norm, target })
}
struct Stagnation {
best: Fix128,
since: u32,
window: u32,
}
impl Stagnation {
fn new(initial: Fix128, window: u32) -> Self {
Self {
best: initial,
since: 0,
window,
}
}
fn record(&mut self, residual: Fix128) -> bool {
if residual < self.best - self.best * STAGNATION_MIN_IMPROVEMENT {
self.best = residual;
self.since = 0;
} else {
if residual < self.best {
self.best = residual;
}
self.since += 1;
}
self.since >= self.window
}
}
struct OperatorGain {
gain: Fix128,
}
impl OperatorGain {
const fn new() -> Self {
Self { gain: Fix128::ZERO }
}
fn observe(&mut self, image: Scaled, input: Scaled) -> Result<bool, LinearSolverError> {
if input.mantissa.is_zero() {
return Ok(true);
}
let ratio = image.ratio(input)?;
if ratio > self.gain {
self.gain = ratio;
}
Ok(self.gain.is_zero() || ratio <= BREAKDOWN_RELATIVE * self.gain)
}
}
fn negligible(value: Fix128, scale: Fix128) -> bool {
if scale.is_zero() {
return true;
}
value.abs() / scale <= BREAKDOWN_RELATIVE
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shift_round_trips_exactly_upwards() {
let x = Fix128::from_ratio(-3, 7);
let up = shift(x, 20).expect("fits");
assert_eq!(shift(up, -20).expect("down"), x);
}
#[test]
fn shift_reports_overflow_instead_of_wrapping() {
assert_eq!(
shift(Fix128::from_int(1 << 40), 30),
Err(LinearSolverError::ArithmeticOverflow)
);
}
#[test]
fn pow2_exponent_brackets_the_magnitude() {
assert_eq!(pow2_exponent(Fix128::ONE), Some(1));
assert_eq!(pow2_exponent(Fix128::from_ratio(3, 4)), Some(0));
assert_eq!(pow2_exponent(Fix128::from_int(-5)), Some(3));
assert_eq!(pow2_exponent(Fix128::ZERO), None);
}
#[test]
fn rescaled_norm_keeps_tiny_vectors() {
let tiny = Fix128::from_raw(0, 1 << 24);
let v = [tiny, tiny, tiny, tiny];
assert_eq!(norm(&v).expect("norm"), tiny.double());
let naive = v.iter().fold(Fix128::ZERO, |s, &x| s + x * x);
assert_eq!(naive, Fix128::ZERO);
}
}