use core::{
cell::Cell,
ops::{Add, AddAssign, Div, Mul, Neg, Sub},
};
use crate::algorithm::{self, DenseL, Numerics, PackedU};
mod sealed {
pub trait Sealed {}
}
pub trait Real:
sealed::Sealed
+ Add<Output = Self>
+ AddAssign
+ Copy
+ Div<Output = Self>
+ Mul<Output = Self>
+ Neg<Output = Self>
+ PartialOrd
+ Sub<Output = Self>
{
const ZERO: Self;
const ONE: Self;
const EPSILON: Self;
const UPDATE_TINY: Self;
fn abs(self) -> Self;
fn is_finite(self) -> bool;
}
macro_rules! real {
($type:ty) => {
impl sealed::Sealed for $type {}
impl Real for $type {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const EPSILON: Self = Self::EPSILON;
const UPDATE_TINY: Self = 1.0e-4;
fn abs(self) -> Self {
self.abs()
}
fn is_finite(self) -> bool {
self.is_finite()
}
}
};
}
real!(f32);
real!(f64);
fn numerics<T: Real>() -> Numerics<T> {
Numerics {
epsilon: T::EPSILON,
update_tiny: T::UPDATE_TINY,
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RowIndex(
pub usize,
);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ColumnIndex(
pub usize,
);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Removal {
pub moved_row: Option<RowIndex>,
pub moved_column: Option<ColumnIndex>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum StorageError {
InvalidDimension,
InsufficientStorage,
SizeOverflow,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StorageLengths {
pub l: usize,
pub u: usize,
}
pub const fn storage_lengths(capacity: usize) -> Result<StorageLengths, StorageError> {
let next = match capacity.checked_add(1) {
Some(next) => next,
None => return Err(StorageError::SizeOverflow),
};
let l = match capacity.checked_mul(next) {
Some(l) => l,
None => return Err(StorageError::SizeOverflow),
};
let u = if capacity % 2 == 0 {
(capacity / 2) * next
} else {
capacity * (next / 2)
};
Ok(StorageLengths { l, u })
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum UpdateError {
CapacityExceeded,
RowOutOfBounds,
ColumnOutOfBounds,
LengthMismatch,
InsufficientWorkspace,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SolveError {
LengthMismatch,
Singular {
index: usize,
},
NonFiniteDiagonal {
index: usize,
},
}
pub struct Workspace<'a, T> {
y: &'a mut [T],
z: &'a mut [T],
w: &'a mut [T],
}
impl<'a, T> Workspace<'a, T> {
pub fn new(y: &'a mut [T], z: &'a mut [T], w: &'a mut [T]) -> Result<Self, StorageError> {
if y.len() != z.len() || y.len() != w.len() {
return Err(StorageError::InsufficientStorage);
}
Ok(Self { y, z, w })
}
fn has_capacity(&self, required: usize) -> bool {
self.y.len() >= required
}
}
pub struct LuMod<'a, T> {
n: usize,
capacity: usize,
l: &'a mut [T],
u: &'a mut [T],
solve_work: &'a [Cell<T>],
}
impl<'a, T: Real> LuMod<'a, T> {
pub fn from_storage(
dimension: usize,
capacity: usize,
l: &'a mut [T],
u: &'a mut [T],
) -> Result<Self, StorageError> {
if dimension > capacity {
return Err(StorageError::InvalidDimension);
}
let lengths = storage_lengths(capacity)?;
let factor_len = lengths.l - capacity;
if l.len() < lengths.l || u.len() < lengths.u {
return Err(StorageError::InsufficientStorage);
}
let (l, remainder) = l.split_at_mut(factor_len);
let solve_work = Cell::from_mut(&mut remainder[..capacity]).as_slice_of_cells();
Ok(Self {
n: dimension,
capacity,
l,
u: &mut u[..lengths.u],
solve_work,
})
}
pub fn dimension(&self) -> usize {
self.n
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn push(
&mut self,
row: &[T],
column: &[T],
diagonal: T,
workspace: &mut Workspace<'_, T>,
) -> Result<(), UpdateError> {
if self.n == self.capacity {
return Err(UpdateError::CapacityExceeded);
}
if row.len() != self.n || column.len() != self.n {
return Err(UpdateError::LengthMismatch);
}
let new_dimension = self.n + 1;
if !workspace.has_capacity(new_dimension) {
return Err(UpdateError::InsufficientWorkspace);
}
workspace.y[..self.n].copy_from_slice(row);
workspace.z[..self.n].copy_from_slice(column);
workspace.y[self.n] = diagonal;
workspace.z[self.n] = diagonal;
let mut l = DenseL::new(&mut *self.l, self.capacity);
let mut u = PackedU::new(&mut *self.u, self.capacity);
algorithm::push(
new_dimension,
&mut l,
&mut u,
workspace.y,
workspace.z,
workspace.w,
numerics(),
);
self.n = new_dimension;
Ok(())
}
pub fn replace_row(
&mut self,
row: RowIndex,
values: &[T],
workspace: &mut Workspace<'_, T>,
) -> Result<(), UpdateError> {
if row.0 >= self.n {
return Err(UpdateError::RowOutOfBounds);
}
self.validate_replacement(values, workspace)?;
workspace.y[..self.n].copy_from_slice(values);
let mut l = DenseL::new(&mut *self.l, self.capacity);
let mut u = PackedU::new(&mut *self.u, self.capacity);
algorithm::replace_row(
row.0,
self.n,
&mut l,
&mut u,
workspace.y,
workspace.z,
workspace.w,
numerics(),
);
Ok(())
}
pub fn replace_column(
&mut self,
column: ColumnIndex,
values: &[T],
workspace: &mut Workspace<'_, T>,
) -> Result<(), UpdateError> {
if column.0 >= self.n {
return Err(UpdateError::ColumnOutOfBounds);
}
self.validate_replacement(values, workspace)?;
workspace.z[..self.n].copy_from_slice(values);
let mut l = DenseL::new(&mut *self.l, self.capacity);
let mut u = PackedU::new(&mut *self.u, self.capacity);
algorithm::replace_column(
column.0,
self.n,
&mut l,
&mut u,
workspace.y,
workspace.z,
workspace.w,
numerics(),
);
Ok(())
}
pub fn remove(
&mut self,
row: RowIndex,
column: ColumnIndex,
workspace: &mut Workspace<'_, T>,
) -> Result<Removal, UpdateError> {
if row.0 >= self.n {
return Err(UpdateError::RowOutOfBounds);
}
if column.0 >= self.n {
return Err(UpdateError::ColumnOutOfBounds);
}
if !workspace.has_capacity(self.n) {
return Err(UpdateError::InsufficientWorkspace);
}
let last = self.n - 1;
let removal = Removal {
moved_row: (row.0 != last).then_some(RowIndex(last)),
moved_column: (column.0 != last).then_some(ColumnIndex(last)),
};
let mut l = DenseL::new(&mut *self.l, self.capacity);
let mut u = PackedU::new(&mut *self.u, self.capacity);
algorithm::remove(
row.0,
column.0,
self.n,
&mut l,
&mut u,
workspace.y,
workspace.z,
workspace.w,
numerics(),
);
self.n = last;
Ok(removal)
}
pub fn solve_in_place(&self, rhs: &mut [T]) -> Result<(), SolveError> {
self.validate_solve(rhs)?;
let l = DenseL::new(&*self.l, self.capacity);
for row in 0..self.n {
self.solve_work[row].set(algorithm::l_product_value(false, self.n, &l, rhs, row));
}
for (value, work) in rhs.iter_mut().zip(self.solve_work) {
*value = work.get();
}
let u = PackedU::new(&*self.u, self.capacity);
algorithm::u_solve(false, self.n, &u, rhs);
Ok(())
}
pub fn solve_transpose_in_place(&self, rhs: &mut [T]) -> Result<(), SolveError> {
self.validate_solve(rhs)?;
let u = PackedU::new(&*self.u, self.capacity);
algorithm::u_solve(true, self.n, &u, rhs);
let l = DenseL::new(&*self.l, self.capacity);
for column in 0..self.n {
self.solve_work[column].set(algorithm::l_product_value(true, self.n, &l, rhs, column));
}
for (value, work) in rhs.iter_mut().zip(self.solve_work) {
*value = work.get();
}
Ok(())
}
fn validate_replacement(
&self,
values: &[T],
workspace: &Workspace<'_, T>,
) -> Result<(), UpdateError> {
if values.len() != self.n {
return Err(UpdateError::LengthMismatch);
}
if !workspace.has_capacity(self.n) {
return Err(UpdateError::InsufficientWorkspace);
}
Ok(())
}
fn validate_solve(&self, rhs: &[T]) -> Result<(), SolveError> {
if rhs.len() != self.n {
return Err(SolveError::LengthMismatch);
}
for index in 0..self.n {
let diagonal = self.u[algorithm::u_index(index, index, self.capacity)];
if diagonal == T::ZERO {
return Err(SolveError::Singular { index });
}
if !diagonal.is_finite() {
return Err(SolveError::NonFiniteDiagonal { index });
}
}
Ok(())
}
}