use core::marker::PhantomData;
use crate::{error::SemiflowError, float::SemiflowFloat, scratch::ScratchPool, state::State};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Growth<F: SemiflowFloat = f64> {
pub multiplier: F,
pub omega: F,
}
impl<F: SemiflowFloat> Growth<F> {
#[inline]
pub fn new(multiplier: F, omega: F) -> Self {
Self { multiplier, omega }
}
#[inline]
#[must_use]
pub fn contraction() -> Self {
Self {
multiplier: F::one(),
omega: F::zero(),
}
}
}
#[allow(clippy::module_name_repetitions)]
pub trait ChernoffFunction<F: SemiflowFloat = f64> {
type S: State<F>;
fn apply_into(
&self,
tau: F,
src: &Self::S,
dst: &mut Self::S,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError>;
fn order(&self) -> u32;
fn growth(&self) -> Growth<F>;
#[allow(unused_variables)]
fn apply_adjoint_into(
&self,
tau: F,
src: &Self::S,
dst: &mut Self::S,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
Err(crate::error::SemiflowError::UnsupportedOperation {
what: "apply_adjoint_into: no transpose-apply primitive for this type; \
implement AdjointApply<F> or use new_self_adjoint for symmetric inners \
(ADR-0114)",
})
}
}
pub trait ApplyChernoffExt<F: SemiflowFloat>: ChernoffFunction<F>
where
Self::S: Clone,
{
fn apply_chernoff(&self, tau: F, src: &Self::S) -> Result<Self::S, SemiflowError> {
let mut dst = src.clone();
let mut scratch = ScratchPool::<F>::new();
self.apply_into(tau, src, &mut dst, &mut scratch)?;
Ok(dst)
}
}
impl<C, F> ApplyChernoffExt<F> for C
where
C: ChernoffFunction<F>,
C::S: Clone,
F: SemiflowFloat,
{
}
#[allow(clippy::module_name_repetitions)]
pub struct Evolver<C, F: SemiflowFloat = f64>
where
C: ChernoffFunction<F>,
{
pub func: C,
pub n: usize,
_phantom: PhantomData<F>,
}
impl<C, F> Evolver<C, F>
where
C: ChernoffFunction<F>,
F: SemiflowFloat,
{
pub fn new(func: C, n: usize) -> Result<Self, SemiflowError> {
if n == 0 {
return Err(SemiflowError::DomainViolation {
what: "Evolver n must be >= 1",
value: 0.0,
});
}
#[cfg(feature = "parallel")]
{
let _ = crate::parallel1d::min_points_per_thread();
let _ = crate::parallel1d::available_parallelism_cached();
}
Ok(Self {
func,
n,
_phantom: PhantomData,
})
}
pub fn evolve_into(
&self,
t: F,
src: &C::S,
dst: &mut C::S,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError>
where
C::S: Clone,
{
validate_t(t)?;
#[allow(clippy::cast_precision_loss)]
let tau = t / crate::float::from_f64::<F>(self.n as f64);
let mut buf_a: C::S = src.clone();
let mut buf_b: C::S = src.clone();
buf_b.zero_into();
let mut src_is_a = true;
for _ in 0..self.n {
if src_is_a {
self.func.apply_into(tau, &buf_a, &mut buf_b, scratch)?;
} else {
self.func.apply_into(tau, &buf_b, &mut buf_a, scratch)?;
}
src_is_a = !src_is_a;
}
let result = if src_is_a { &buf_a } else { &buf_b };
dst.copy_from(result);
Ok(())
}
pub fn evolve(&self, t: F, src: &C::S) -> Result<C::S, SemiflowError>
where
C::S: Clone,
{
let mut dst = src.clone();
let mut scratch = ScratchPool::<F>::new();
self.evolve_into(t, src, &mut dst, &mut scratch)?;
Ok(dst)
}
}
pub struct ChernoffSemigroup<C, S>
where
C: ChernoffFunction<f64, S = S>,
S: State<f64> + Clone,
{
pub func: C,
pub n: usize,
_state: PhantomData<S>,
}
impl<C, S> ChernoffSemigroup<C, S>
where
C: ChernoffFunction<f64, S = S>,
S: State<f64> + Clone,
{
pub fn new(func: C, n: usize) -> Result<Self, SemiflowError> {
if n == 0 {
return Err(SemiflowError::DomainViolation {
what: "ChernoffSemigroup n must be >= 1",
value: 0.0,
});
}
#[cfg(feature = "parallel")]
{
let _ = crate::parallel1d::min_points_per_thread();
let _ = crate::parallel1d::available_parallelism_cached();
}
Ok(Self {
func,
n,
_state: PhantomData,
})
}
pub fn evolve(&self, t: f64, f: &S) -> Result<S, SemiflowError> {
if !t.is_finite() || t < 0.0 {
return Err(SemiflowError::DomainViolation {
what: "t must be finite and >= 0",
value: t,
});
}
#[allow(clippy::cast_precision_loss)]
let tau = t / self.n as f64;
let mut buf_a: S = f.clone();
let mut buf_b: S = f.clone();
buf_b.zero_into();
let mut scratch: ScratchPool<f64> = ScratchPool::new();
let mut src_is_a = true;
for _ in 0..self.n {
if src_is_a {
self.func
.apply_into(tau, &buf_a, &mut buf_b, &mut scratch)?;
} else {
self.func
.apply_into(tau, &buf_b, &mut buf_a, &mut scratch)?;
}
src_is_a = !src_is_a;
}
Ok(if src_is_a { buf_a } else { buf_b })
}
}
#[inline]
fn validate_t<F: SemiflowFloat>(t: F) -> Result<(), SemiflowError> {
if !t.is_finite() || t < F::zero() {
return Err(SemiflowError::DomainViolation {
what: "t must be finite and >= 0",
value: t.to_f64().unwrap_or(f64::NAN),
});
}
Ok(())
}