#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use super::{
bicgstab, gmres, shift, KrylovConfig, KrylovSolution, LinearOperator, LinearSolverError,
Preconditioner,
};
use crate::coupled_iteration::{ConfigFault, EquilibrationScale, L2_TERM_FLOOR};
use crate::math::Fix128;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BlockScale {
Down(EquilibrationScale),
Up(EquilibrationScale),
}
impl BlockScale {
fn apply(self, value: Fix128) -> Fix128 {
match self {
Self::Down(s) => s.scale_down(value),
Self::Up(s) => s.scale_up(value),
}
}
fn apply_squared(self, value: Fix128) -> Result<Fix128, LinearSolverError> {
let e = i32::try_from(match self {
Self::Down(s) | Self::Up(s) => s.exponent(),
})
.unwrap_or(i32::MAX);
match self {
Self::Down(_) => shift(value, -2 * e),
Self::Up(_) => shift(value, 2 * e),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum KrylovMethod {
Gmres,
BiCgStab,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlockEquilibration {
offsets: Vec<usize>,
scales: Vec<BlockScale>,
}
impl BlockEquilibration {
pub fn try_new(
block_sizes: &[usize],
diagonal_magnitudes: &[Fix128],
) -> Result<Self, LinearSolverError> {
let offsets = layout(block_sizes, diagonal_magnitudes)?;
let mut scales = Vec::with_capacity(block_sizes.len());
for &m in diagonal_magnitudes {
scales.push(scale_for(m)?);
}
let equilibration = Self { offsets, scales };
equilibration.check_floor(diagonal_magnitudes)?;
Ok(equilibration)
}
pub fn unscaled(
block_sizes: &[usize],
diagonal_magnitudes: &[Fix128],
) -> Result<Self, LinearSolverError> {
let offsets = layout(block_sizes, diagonal_magnitudes)?;
let equilibration = Self {
offsets,
scales: vec![BlockScale::Down(EquilibrationScale::IDENTITY); block_sizes.len()],
};
equilibration.check_floor(diagonal_magnitudes)?;
Ok(equilibration)
}
#[must_use]
pub fn dim(&self) -> usize {
self.offsets.last().copied().unwrap_or(0)
}
#[must_use]
pub fn block_count(&self) -> usize {
self.scales.len()
}
#[must_use]
pub fn scale(&self, block: usize) -> Option<BlockScale> {
self.scales.get(block).copied()
}
pub fn equilibrate_diagonal(
&self,
diagonal: &[Fix128],
) -> Result<Vec<Fix128>, LinearSolverError> {
if diagonal.len() != self.dim() {
return Err(LinearSolverError::DimensionMismatch {
expected: self.dim(),
found: diagonal.len(),
});
}
let mut out = Vec::with_capacity(diagonal.len());
for (block, &scale) in self.scales.iter().enumerate() {
for &a in &diagonal[self.offsets[block]..self.offsets[block + 1]] {
out.push(scale.apply_squared(a)?);
}
}
Ok(out)
}
fn apply(&self, v: &mut [Fix128]) {
for (block, &scale) in self.scales.iter().enumerate() {
for value in &mut v[self.offsets[block]..self.offsets[block + 1]] {
*value = scale.apply(*value);
}
}
}
fn check_floor(&self, magnitudes: &[Fix128]) -> Result<(), LinearSolverError> {
let mut scaled = Vec::with_capacity(magnitudes.len());
for (&m, &scale) in magnitudes.iter().zip(&self.scales) {
scaled.push(scale.apply_squared(m)?);
}
let largest = scaled
.iter()
.copied()
.fold(Fix128::ZERO, |a, b| if b > a { b } else { a });
for (block, &m) in scaled.iter().enumerate() {
let relative_magnitude = m / largest;
if relative_magnitude < L2_TERM_FLOOR {
return Err(LinearSolverError::BlockBelowProductFloor {
block,
relative_magnitude,
});
}
}
Ok(())
}
}
fn layout(block_sizes: &[usize], magnitudes: &[Fix128]) -> Result<Vec<usize>, LinearSolverError> {
if block_sizes.is_empty()
|| block_sizes.len() != magnitudes.len()
|| block_sizes.contains(&0)
|| magnitudes.iter().any(|m| *m <= Fix128::ZERO)
{
return Err(LinearSolverError::InvalidBlockLayout);
}
let mut offsets = Vec::with_capacity(block_sizes.len() + 1);
let mut offset = 0usize;
offsets.push(0);
for &size in block_sizes {
offset = offset
.checked_add(size)
.ok_or(LinearSolverError::InvalidBlockLayout)?;
offsets.push(offset);
}
Ok(offsets)
}
fn scale_for(m: Fix128) -> Result<BlockScale, LinearSolverError> {
if m >= Fix128::ONE {
let s = EquilibrationScale::covering(m.sqrt()).map_err(LinearSolverError::Equilibration)?;
Ok(BlockScale::Down(s))
} else {
let limit = Fix128::from_raw(0, 1 << 2);
if m < limit {
return Err(LinearSolverError::Equilibration(
ConfigFault::ScaleOutOfRange,
));
}
let s = EquilibrationScale::covering((Fix128::ONE / m).sqrt())
.map_err(LinearSolverError::Equilibration)?;
Ok(BlockScale::Up(s))
}
}
struct Equilibrated<'a, A: ?Sized> {
op: &'a A,
equilibration: &'a BlockEquilibration,
}
impl<A: LinearOperator + ?Sized> LinearOperator for Equilibrated<'_, A> {
fn dim(&self) -> usize {
self.op.dim()
}
fn apply(&self, x: &[Fix128], y: &mut [Fix128]) {
let mut scaled = x.to_vec();
self.equilibration.apply(&mut scaled);
self.op.apply(&scaled, y);
self.equilibration.apply(y);
}
}
pub fn solve_equilibrated<A: LinearOperator + ?Sized, M: Preconditioner + ?Sized>(
op: &A,
equilibration: &BlockEquilibration,
precond: &M,
b: &[Fix128],
method: KrylovMethod,
config: &KrylovConfig,
) -> Result<KrylovSolution, LinearSolverError> {
let n = op.dim();
if equilibration.dim() != n {
return Err(LinearSolverError::DimensionMismatch {
expected: n,
found: equilibration.dim(),
});
}
if b.len() != n {
return Err(LinearSolverError::DimensionMismatch {
expected: n,
found: b.len(),
});
}
let scaled_op = Equilibrated { op, equilibration };
let mut scaled_b = b.to_vec();
equilibration.apply(&mut scaled_b);
let mut solution = match method {
KrylovMethod::Gmres => gmres(&scaled_op, precond, &scaled_b, config)?,
KrylovMethod::BiCgStab => bicgstab(&scaled_op, precond, &scaled_b, config)?,
};
equilibration.apply(&mut solution.x);
Ok(solution)
}