#![deny(rustdoc::broken_intra_doc_links)]
use crate::{
FpScalar, RealScalar,
core::traits::validation::sealed,
functions::{Abs, Pow},
scalars::NonNegativeRealScalar,
};
use getset::Getters;
use num::{Complex, One, Zero};
use rayon::prelude::*;
use std::ops::{Add, AddAssign, Mul, MulAssign};
pub trait Accumulator: Sized {
type Input;
type Output;
fn new() -> Self;
fn push(&mut self, value: Self::Input);
fn combine(&mut self, other: Self);
fn result(self) -> Self::Output;
fn new_sequential<I>(values: I) -> Self
where
I: IntoIterator<Item = Self::Input>,
{
let mut acc = Self::new();
values.into_iter().for_each(|v| acc.push(v));
acc
}
fn new_parallel<I>(values: I) -> Self
where
Self: Send,
I: IntoParallelIterator<Item = Self::Input>,
{
values
.into_par_iter()
.fold(
|| Self::new(),
|mut acc, item| {
acc.push(item);
acc
},
)
.reduce(
|| Self::new(),
|mut a, b| {
a.combine(b);
a
},
)
}
}
pub trait SumAccumulator:
Accumulator<Input: Zero + Clone, Output = <Self as Accumulator>::Input>
{
fn rescale_by(&mut self, r: &Self::Input);
}
pub struct NaiveSum<T>(T);
impl<T> Accumulator for NaiveSum<T>
where
T: Zero + Clone + for<'a> Add<&'a T, Output = T> + for<'a> Mul<&'a T, Output = T> + AddAssign,
{
type Input = T;
type Output = T;
#[inline(always)]
fn new() -> Self {
NaiveSum(T::zero())
}
#[inline(always)]
fn push(&mut self, r: T) {
self.0 += r;
}
#[inline(always)]
fn result(self) -> T {
self.0
}
#[inline(always)]
fn combine(&mut self, other: Self) {
self.0 += other.0;
}
}
impl<T> SumAccumulator for NaiveSum<T>
where
T: Zero
+ Clone
+ for<'a> Add<&'a T, Output = T>
+ for<'a> Mul<&'a T, Output = T>
+ AddAssign
+ for<'a> MulAssign<&'a T>,
{
#[inline(always)]
fn rescale_by(&mut self, r: &T) {
self.0 *= r;
}
}
#[inline(always)]
fn neumaier_sum_and_compensation_native_real<T>(value: T, sum: &mut T, compensation: &mut T)
where
T: sealed::NativeReal + AddAssign,
{
let sum_before_compensation = *sum;
*sum += value;
*compensation += if sum_before_compensation.abs() >= value.abs() {
(sum_before_compensation - *sum) + value
} else {
(value - *sum) + sum_before_compensation
};
}
#[inline(always)]
pub(crate) fn neumaier_sum_and_compensation_real<RealType>(
value: RealType,
sum: &mut RealType,
compensation: &mut RealType,
) where
RealType: RealScalar,
{
let sum_before_compensation = sum.clone();
*sum += &value;
*compensation += if sum_before_compensation.clone().abs() >= value.clone().abs() {
(sum_before_compensation - &*sum) + value
} else {
(value - &*sum) + sum_before_compensation
};
}
pub trait NeumaierAddable: Sized {
fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self);
}
impl<T> NeumaierAddable for T
where
T: sealed::NativeReal + AddAssign,
{
#[inline(always)]
fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
neumaier_sum_and_compensation_native_real(value, sum, compensation)
}
}
impl<T> NeumaierAddable for Complex<T>
where
T: sealed::NativeReal + AddAssign,
{
#[inline(always)]
fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
neumaier_sum_and_compensation_native_real(value.re, &mut sum.re, &mut compensation.re);
neumaier_sum_and_compensation_native_real(value.im, &mut sum.im, &mut compensation.im);
}
}
#[cfg(feature = "rug")]
mod rug_impls {
use super::*;
#[inline(always)]
fn neumaier_sum_and_compensation_rug_float(
value: rug::Float,
sum: &mut rug::Float,
compensation: &mut rug::Float,
) {
let sum_before_compensation = sum.clone();
*sum += &value;
*compensation += if sum_before_compensation.clone().abs() >= value.clone().abs() {
(sum_before_compensation - &*sum) + value
} else {
(value - &*sum) + sum_before_compensation
};
}
impl NeumaierAddable for rug::Float {
#[inline(always)]
fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
neumaier_sum_and_compensation_rug_float(value, sum, compensation)
}
}
impl NeumaierAddable for rug::Complex {
#[inline(always)]
fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
let (value_real, value_imag) = value.into_real_imag();
neumaier_sum_and_compensation_rug_float(
value_real,
sum.mut_real(),
compensation.mut_real(),
);
neumaier_sum_and_compensation_rug_float(
value_imag,
sum.mut_imag(),
compensation.mut_imag(),
);
}
}
}
#[derive(Debug, Clone, Getters)]
pub struct NeumaierSum<ScalarType> {
#[getset(get = "pub")]
sum_before_compensation: ScalarType,
#[getset(get = "pub")]
compensation: ScalarType,
}
impl<ScalarType> Accumulator for NeumaierSum<ScalarType>
where
ScalarType: Clone
+ Zero
+ for<'a> Add<&'a ScalarType, Output = ScalarType>
+ for<'a> AddAssign<&'a ScalarType>
+ for<'a> Mul<&'a ScalarType, Output = ScalarType>
+ for<'a> MulAssign<&'a ScalarType>
+ NeumaierAddable,
{
type Input = ScalarType;
type Output = ScalarType;
#[inline(always)]
fn new() -> Self {
Self {
sum_before_compensation: ScalarType::zero(),
compensation: ScalarType::zero(),
}
}
#[inline(always)]
fn push(&mut self, value: Self::Input) {
<ScalarType as NeumaierAddable>::neumaier_compensated_sum(
value,
&mut self.sum_before_compensation,
&mut self.compensation,
);
}
#[inline(always)]
fn result(self) -> Self::Input {
self.sum_before_compensation + self.compensation
}
#[inline(always)]
fn combine(&mut self, other: Self) {
self.push(other.sum_before_compensation);
self.push(other.compensation);
}
}
impl<ScalarType> SumAccumulator for NeumaierSum<ScalarType>
where
ScalarType: Clone
+ Zero
+ for<'a> Add<&'a ScalarType, Output = ScalarType>
+ for<'a> AddAssign<&'a ScalarType>
+ for<'a> Mul<&'a ScalarType, Output = ScalarType>
+ for<'a> MulAssign<&'a ScalarType>
+ NeumaierAddable,
{
#[inline(always)]
fn rescale_by(&mut self, r: &Self::Input) {
self.sum_before_compensation += &self.compensation;
self.sum_before_compensation *= r;
self.compensation = ScalarType::zero();
}
}
pub struct MaxAccumulator<RealType: RealScalar> {
max_value: RealType,
}
impl<RealType: RealScalar> Accumulator for MaxAccumulator<RealType> {
type Input = RealType;
type Output = RealType;
#[inline(always)]
fn new() -> Self {
Self {
max_value: RealType::min_finite(),
}
}
#[inline(always)]
fn push(&mut self, value: Self::Input) {
if value > self.max_value {
self.max_value = value;
}
}
#[inline(always)]
fn result(self) -> Self::Output {
self.max_value
}
#[inline(always)]
fn combine(&mut self, other: Self) {
if other.max_value > self.max_value {
self.max_value = other.max_value;
}
}
}
pub struct MinAccumulator<RealType: RealScalar> {
min_value: RealType,
}
impl<RealType: RealScalar> Accumulator for MinAccumulator<RealType> {
type Input = RealType;
type Output = RealType;
#[inline(always)]
fn new() -> Self {
Self {
min_value: RealType::max_finite(),
}
}
#[inline(always)]
fn push(&mut self, value: Self::Input) {
if value < self.min_value {
self.min_value = value;
}
}
#[inline(always)]
fn result(self) -> Self::Output {
self.min_value
}
#[inline(always)]
fn combine(&mut self, other: Self) {
if other.min_value < self.min_value {
self.min_value = other.min_value;
}
}
}
pub struct MaxAbsValueAccumulator<ScalarType: FpScalar> {
max_abs: NonNegativeRealScalar<ScalarType::RealType>, }
impl<ScalarType: FpScalar> Accumulator for MaxAbsValueAccumulator<ScalarType> {
type Input = ScalarType;
type Output = NonNegativeRealScalar<ScalarType::RealType>;
fn new() -> Self {
Self {
max_abs: NonNegativeRealScalar::zero(),
}
}
fn push(&mut self, value: ScalarType) {
let abs_v = value.abs();
if abs_v > self.max_abs {
self.max_abs = abs_v;
}
}
fn combine(&mut self, other: Self) {
if other.max_abs > self.max_abs {
self.max_abs = other.max_abs;
}
}
fn result(self) -> NonNegativeRealScalar<ScalarType::RealType> {
self.max_abs
}
}
pub struct ScaledSumSqAccumulator<SumSqAcc>
where
SumSqAcc: SumAccumulator<Input: RealScalar>,
{
scale: NonNegativeRealScalar<SumSqAcc::Input>,
sumsq_accumulator: SumSqAcc,
zero: NonNegativeRealScalar<SumSqAcc::Input>,
}
impl<SumSqAcc> ScaledSumSqAccumulator<SumSqAcc>
where
SumSqAcc: SumAccumulator<Input: RealScalar>,
{
fn add_value_and_update_scale(&mut self, value: NonNegativeRealScalar<SumSqAcc::Input>) {
if value > self.zero {
let r_sq = if self.scale < value {
if self.scale > self.zero {
let r = std::mem::replace(&mut self.scale, value).into_inner()
/ self.scale.as_ref();
self.sumsq_accumulator.rescale_by(&r.pow(2));
} else {
self.scale = value;
}
SumSqAcc::Input::one()
} else {
(value.into_inner() / self.scale.as_ref()).pow(2)
};
self.sumsq_accumulator.push(r_sq);
}
}
pub fn result_sqrt(self) -> NonNegativeRealScalar<SumSqAcc::Input> {
if self.scale == self.zero {
self.zero
} else {
NonNegativeRealScalar::new(self.sumsq_accumulator.result()).sqrt() * self.scale
}
}
pub fn new_sequential<I>(values: I) -> Self
where
I: IntoIterator<Item = NonNegativeRealScalar<SumSqAcc::Input>>,
{
let mut acc = Self::new();
values.into_iter().for_each(|v| acc.push(v));
acc
}
pub fn new_sequential_from_scalars<I>(values: I) -> Self
where
I: IntoIterator<Item: FpScalar<RealType = SumSqAcc::Input>>,
{
let mut acc = Self::new();
values.into_iter().for_each(|v| {
acc.push(v.abs());
});
acc
}
pub fn new_parallel_from_scalars<I>(values: I) -> Self
where
Self: Send,
I: IntoParallelIterator<Item: FpScalar<RealType = SumSqAcc::Input>>,
{
values
.into_par_iter()
.fold(Self::new, |mut acc, item| {
acc.push(item.abs());
acc
})
.reduce(Self::new, |mut a, b| {
a.combine(b);
a
})
}
}
impl<SumSqAcc> Accumulator for ScaledSumSqAccumulator<SumSqAcc>
where
SumSqAcc: SumAccumulator<Input: RealScalar>,
{
type Input = NonNegativeRealScalar<SumSqAcc::Input>;
type Output = NonNegativeRealScalar<SumSqAcc::Input>;
fn new() -> Self {
Self {
scale: NonNegativeRealScalar::<SumSqAcc::Input>::zero(),
sumsq_accumulator: SumSqAcc::new(),
zero: NonNegativeRealScalar::<SumSqAcc::Input>::zero(),
}
}
fn push(&mut self, value: Self::Input) {
self.add_value_and_update_scale(value);
}
fn combine(&mut self, mut other: Self) {
if other.scale > self.scale {
if self.scale > self.zero {
let r = std::mem::replace(&mut self.scale, other.scale).into_inner()
/ self.scale.as_ref();
self.sumsq_accumulator.rescale_by(&r.pow(2));
} else {
self.scale = other.scale;
}
self.sumsq_accumulator.combine(other.sumsq_accumulator);
} else if other.scale > self.zero {
if other.scale < self.scale {
let r = other.scale.into_inner() / self.scale.as_ref();
other.sumsq_accumulator.rescale_by(&r.pow(2));
}
self.sumsq_accumulator.combine(other.sumsq_accumulator);
}
}
fn result(self) -> Self::Output {
if self.scale == self.zero {
self.zero
} else {
NonNegativeRealScalar::new(
self.sumsq_accumulator.result() * self.scale.into_inner().pow(2),
)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod neumaier_sum {
use super::*;
mod native {
use super::*;
mod real {
use super::*;
#[test]
fn new() {
let neumaier = NeumaierSum::<f64>::new();
assert_eq!(neumaier.sum_before_compensation, 0.0);
assert_eq!(neumaier.compensation, 0.0);
}
#[test]
fn add() {
let mut neumaier = NeumaierSum::<f64>::new();
neumaier.push(1.0);
neumaier.push(1e-16);
neumaier.push(-1.0);
assert_eq!(neumaier.sum_before_compensation, 0.0);
assert_eq!(neumaier.compensation, 1e-16);
}
#[test]
fn sum() {
let mut neumaier = NeumaierSum::<f64>::new();
neumaier.push(1.0);
neumaier.push(1e-16);
neumaier.push(-1.0);
assert_eq!(neumaier.sum_before_compensation, 0.0);
assert_eq!(neumaier.compensation, 1e-16);
let sum = neumaier.result();
assert_eq!(sum, 1e-16);
println!("compensated sum = {}", sum);
}
#[test]
fn sum_big_values() {
let values = vec![1.0, 1e100, 1.0, -1e100];
let sum = values.iter().sum::<f64>();
assert_eq!(sum, 0.0);
let neumaier = NeumaierSum::<f64>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(sum, 2.0);
println!("compensated sum = {}", sum);
}
#[test]
fn sum_small_values() {
let values = [1.0, 1e-100, -1.0];
let sum = values.iter().sum::<f64>();
assert_eq!(sum, 0.0);
let neumaier = NeumaierSum::<f64>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(sum, 1e-100);
println!("compensated sum = {}", sum);
}
#[test]
fn combine_partial_sums() {
let mut left = NaiveSum::<f64>::new();
left.push(1.0);
left.push(2.0);
let mut right = NaiveSum::<f64>::new();
right.push(3.0);
right.push(4.0);
left.combine(right);
assert_eq!(left.result(), 10.0);
}
#[test]
fn combine_partial_neumaier_sums() {
let mut left = NeumaierSum::<f64>::new();
left.push(1.0);
left.push(1e-16);
let mut right = NeumaierSum::<f64>::new();
right.push(2.0);
right.push(1e-16);
left.combine(right);
let sum = left.result();
assert!((sum - (3.0 + 2e-16)).abs() < 1e-15);
}
#[test]
fn combine_matches_new_sequential_for_chunks() {
let values = [1.0_f64, 1e100, 1.0, -1e100, 1e-16, 2.0];
let mut chunk_1 = NaiveSum::<f64>::new();
chunk_1.push(values[0]);
chunk_1.push(values[1]);
let mut chunk_2 = NaiveSum::<f64>::new();
chunk_2.push(values[2]);
chunk_2.push(values[3]);
let mut chunk_3 = NaiveSum::<f64>::new();
chunk_3.push(values[4]);
chunk_3.push(values[5]);
chunk_1.combine(chunk_2);
chunk_1.combine(chunk_3);
let sequential = NaiveSum::<f64>::new_sequential(values);
assert_eq!(chunk_1.result(), sequential.result());
}
#[test]
fn combine_is_order_dependent_for_naive_sum() {
let mut left_grouped = NaiveSum::<f64>::new();
left_grouped.push(1e16);
let mut middle = NaiveSum::<f64>::new();
middle.push(1.0);
let mut right_last = NaiveSum::<f64>::new();
right_last.push(-1e16);
left_grouped.combine(middle);
left_grouped.combine(right_last);
let left_result = left_grouped.result();
let mut right_grouped = NaiveSum::<f64>::new();
right_grouped.push(1e16);
let mut right_last = NaiveSum::<f64>::new();
right_last.push(-1e16);
let mut middle = NaiveSum::<f64>::new();
middle.push(1.0);
right_grouped.combine(right_last);
right_grouped.combine(middle);
let right_result = right_grouped.result();
assert_eq!(left_result, 0.0);
assert_eq!(right_result, 1.0);
assert_ne!(left_result, right_result);
}
#[test]
fn neumaier_combine_matches_new_sequential_for_chunks() {
let values = [1.0_f64, 1e100, 1.0, -1e100, 1e-16, 2.0];
let mut chunk_1 = NeumaierSum::<f64>::new();
chunk_1.push(values[0]);
chunk_1.push(values[1]);
let mut chunk_2 = NeumaierSum::<f64>::new();
chunk_2.push(values[2]);
chunk_2.push(values[3]);
let mut chunk_3 = NeumaierSum::<f64>::new();
chunk_3.push(values[4]);
chunk_3.push(values[5]);
chunk_1.combine(chunk_2);
chunk_1.combine(chunk_3);
let sequential = NeumaierSum::<f64>::new_sequential(values);
assert_eq!(chunk_1.result(), sequential.result());
}
#[test]
fn neumaier_combine_is_stable_across_orders() {
let mut left_order = NeumaierSum::<f64>::new();
left_order.push(1e16);
let mut middle = NeumaierSum::<f64>::new();
middle.push(1.0);
let mut right_last = NeumaierSum::<f64>::new();
right_last.push(-1e16);
left_order.combine(middle);
left_order.combine(right_last);
let left_result = left_order.result();
let mut right_order = NeumaierSum::<f64>::new();
right_order.push(1e16);
let mut right_last = NeumaierSum::<f64>::new();
right_last.push(-1e16);
let mut middle = NeumaierSum::<f64>::new();
middle.push(1.0);
right_order.combine(right_last);
right_order.combine(middle);
let right_result = right_order.result();
assert_eq!(left_result, 1.0);
assert_eq!(right_result, 1.0);
assert_eq!(left_result, right_result);
}
}
mod complex {
use super::*;
use num::Complex;
#[test]
fn new() {
let neumaier = NeumaierSum::<Complex<f64>>::new();
let zero = Complex::new(0.0, 0.0);
assert_eq!(&neumaier.sum_before_compensation, &zero);
assert_eq!(&neumaier.compensation, &zero);
}
#[test]
fn add() {
let mut neumaier = NeumaierSum::<Complex<f64>>::new();
let zero = Complex::new(0.0, 0.0);
let v = Complex::new(1e-16, 2e-16);
neumaier.push(Complex::new(1.0, 2.0));
neumaier.push(v);
neumaier.push(Complex::new(-1.0, -2.0));
assert_eq!(neumaier.sum_before_compensation, zero);
assert_eq!(neumaier.compensation, v);
}
#[test]
fn sum() {
let mut neumaier = NeumaierSum::<Complex<f64>>::new();
let zero = Complex::new(0.0, 0.0);
let v = Complex::new(1e-16, 2e-16);
neumaier.push(Complex::new(1.0, 2.0));
neumaier.push(v);
neumaier.push(Complex::new(-1.0, -2.0));
assert_eq!(neumaier.sum_before_compensation, zero);
assert_eq!(neumaier.compensation, v);
let sum = neumaier.result();
assert_eq!(sum, v);
println!("compensated sum = {}", sum);
}
#[test]
fn sum_big_values() {
let values = vec![
Complex::new(1.0, 2.0),
Complex::new(1e100, 2e100),
Complex::new(1.0, 2.0),
Complex::new(-1e100, -2e100),
];
let sum = values.iter().sum::<Complex<f64>>();
assert_eq!(sum, Complex::new(0.0, 0.0));
let neumaier = NeumaierSum::<Complex<f64>>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(sum, Complex::new(2.0, 4.0));
println!("compensated sum = {}", sum);
}
#[test]
fn sum_small_values() {
let v = Complex::new(1e-100, 2e-100);
let values = [Complex::new(1.0, 2.0), v, Complex::new(-1.0, -2.0)];
let sum = values.iter().sum::<Complex<f64>>();
assert_eq!(sum, Complex::new(0.0, 0.0));
let neumaier = NeumaierSum::<Complex<f64>>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(sum, v);
println!("compensated sum = {}", sum);
}
#[test]
fn combine_partial_sums() {
let mut left = NaiveSum::<Complex<f64>>::new();
left.push(Complex::new(1.0, 2.0));
let mut right = NaiveSum::<Complex<f64>>::new();
right.push(Complex::new(3.0, 4.0));
left.combine(right);
assert_eq!(left.result(), Complex::new(4.0, 6.0));
}
#[test]
fn combine_partial_neumaier_sums() {
let mut left = NeumaierSum::<Complex<f64>>::new();
left.push(Complex::new(1.0, 2.0));
left.push(Complex::new(1e-16, 2e-16));
let mut right = NeumaierSum::<Complex<f64>>::new();
right.push(Complex::new(3.0, 4.0));
right.push(Complex::new(1e-16, 2e-16));
left.combine(right);
assert_eq!(
left.result(),
Complex::new(4.0, 6.0) + Complex::new(2e-16, 4e-16)
);
}
#[test]
fn combine_matches_new_sequential_for_chunks() {
let values = [
Complex::new(1.0, 2.0),
Complex::new(1e100, 2e100),
Complex::new(1.0, 2.0),
Complex::new(-1e100, -2e100),
Complex::new(1e-16, 2e-16),
Complex::new(2.0, 3.0),
];
let mut chunk_1 = NaiveSum::<Complex<f64>>::new();
chunk_1.push(values[0]);
chunk_1.push(values[1]);
let mut chunk_2 = NaiveSum::<Complex<f64>>::new();
chunk_2.push(values[2]);
chunk_2.push(values[3]);
let mut chunk_3 = NaiveSum::<Complex<f64>>::new();
chunk_3.push(values[4]);
chunk_3.push(values[5]);
chunk_1.combine(chunk_2);
chunk_1.combine(chunk_3);
let sequential = NaiveSum::<Complex<f64>>::new_sequential(values);
assert_eq!(chunk_1.result(), sequential.result());
}
#[test]
fn neumaier_combine_matches_new_sequential_for_chunks() {
let values = [
Complex::new(1.0, 2.0),
Complex::new(1e100, 2e100),
Complex::new(1.0, 2.0),
Complex::new(-1e100, -2e100),
Complex::new(1e-16, 2e-16),
Complex::new(2.0, 3.0),
];
let mut chunk_1 = NeumaierSum::<Complex<f64>>::new();
chunk_1.push(values[0]);
chunk_1.push(values[1]);
let mut chunk_2 = NeumaierSum::<Complex<f64>>::new();
chunk_2.push(values[2]);
chunk_2.push(values[3]);
let mut chunk_3 = NeumaierSum::<Complex<f64>>::new();
chunk_3.push(values[4]);
chunk_3.push(values[5]);
chunk_1.combine(chunk_2);
chunk_1.combine(chunk_3);
let sequential = NeumaierSum::<Complex<f64>>::new_sequential(values);
assert_eq!(chunk_1.result(), sequential.result());
}
}
}
#[cfg(feature = "rug")]
mod rug100 {
use super::*;
use crate::{ComplexRugStrictFinite, RealRugStrictFinite};
use try_create::TryNew;
const PRECISION: u32 = 100;
mod real {
use super::*;
use crate::RealScalar;
#[test]
fn new() {
let neumaier = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
assert!(neumaier.sum_before_compensation.is_zero());
assert!(neumaier.compensation.is_zero());
}
#[test]
fn add() {
let mut neumaier = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
let v = RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
PRECISION,
rug::Float::parse("1e-100").unwrap(),
))
.expect("valid test value");
neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
neumaier.push(v.clone());
neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1.0).unwrap());
assert_eq!(
neumaier.sum_before_compensation,
RealRugStrictFinite::<PRECISION>::try_from_f64(0.0).unwrap()
);
assert_eq!(&neumaier.compensation, &v);
}
#[test]
fn sum() {
let mut neumaier = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
let v = RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
PRECISION,
rug::Float::parse("1e-100").unwrap(),
))
.expect("valid test value");
neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
neumaier.push(v.clone());
neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1.0).unwrap());
assert!(neumaier.sum_before_compensation.is_zero());
assert_eq!(&neumaier.compensation, &v);
let sum = neumaier.result();
assert_eq!(sum, v);
println!("compensated sum = {}", sum);
}
#[test]
fn sum_big_values() {
let values = ["1.0", "1e100", "1.0", "-1e100"]
.iter()
.map(|v| {
RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
PRECISION,
rug::Float::parse(v).unwrap(),
))
.expect("valid test value")
})
.collect::<Vec<_>>();
let sum = values
.iter()
.fold(RealRugStrictFinite::<PRECISION>::zero(), |acc, x| acc + x);
assert!(sum.is_zero());
let neumaier =
NeumaierSum::<RealRugStrictFinite<PRECISION>>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(
sum,
RealRugStrictFinite::<PRECISION>::try_from_f64(2.0).unwrap()
);
println!("compensated sum = {}", sum);
}
#[test]
fn sum_small_values() {
let values = ["1.0", "1e-100", "-1.0"]
.iter()
.map(|v| {
RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
PRECISION,
rug::Float::parse(v).unwrap(),
))
.expect("valid test value")
})
.collect::<Vec<_>>();
let sum = values
.iter()
.fold(RealRugStrictFinite::<PRECISION>::zero(), |acc, x| acc + x);
assert_eq!(sum, RealRugStrictFinite::<PRECISION>::zero());
let neumaier =
NeumaierSum::<RealRugStrictFinite<PRECISION>>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(
sum,
RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
PRECISION,
rug::Float::parse("1e-100").unwrap(),
))
.expect("valid test value")
);
println!("compensated sum = {}", sum);
}
#[test]
fn combine_matches_new_sequential_for_chunks() {
let values = ["1.0", "1e100", "1.0", "-1e100", "1e-100", "2.0"]
.iter()
.map(|v| {
RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
PRECISION,
rug::Float::parse(v).unwrap(),
))
.expect("valid test value")
})
.collect::<Vec<_>>();
let mut chunk_1 = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
chunk_1.push(values[0].clone());
chunk_1.push(values[1].clone());
let mut chunk_2 = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
chunk_2.push(values[2].clone());
chunk_2.push(values[3].clone());
let mut chunk_3 = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
chunk_3.push(values[4].clone());
chunk_3.push(values[5].clone());
chunk_1.combine(chunk_2);
chunk_1.combine(chunk_3);
let sequential =
NeumaierSum::<RealRugStrictFinite<PRECISION>>::new_sequential(values);
assert_eq!(chunk_1.result(), sequential.result());
}
#[test]
fn neumaier_combine_is_stable_across_orders() {
let mut left_order = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
left_order.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1e16).unwrap());
let mut middle = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
middle.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
let mut right_last = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
right_last.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1e16).unwrap());
left_order.combine(middle);
left_order.combine(right_last);
let left_result = left_order.result();
let mut right_order = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
right_order.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1e16).unwrap());
let mut right_last = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
right_last.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1e16).unwrap());
let mut middle = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
middle.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
right_order.combine(right_last);
right_order.combine(middle);
let right_result = right_order.result();
assert_eq!(
left_result,
RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap()
);
assert_eq!(
right_result,
RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap()
);
assert_eq!(left_result, right_result);
}
}
mod complex {
use super::*;
#[test]
fn new() {
let neumaier = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
assert_eq!(
neumaier.sum_before_compensation,
ComplexRugStrictFinite::<PRECISION>::zero()
);
assert_eq!(
neumaier.compensation,
ComplexRugStrictFinite::<PRECISION>::zero()
);
}
#[test]
fn add() {
let mut neumaier = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
let v = ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
PRECISION,
rug::Complex::parse("(1e-100,2e-100)").unwrap(),
))
.expect("valid test value");
neumaier.push(
ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(1.0, 2.0))
.unwrap(),
);
neumaier.push(v.clone());
neumaier.push(
ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(-1.0, -2.0))
.unwrap(),
);
assert_eq!(
neumaier.sum_before_compensation,
ComplexRugStrictFinite::<PRECISION>::zero()
);
assert_eq!(&neumaier.compensation, &v);
}
#[test]
fn sum() {
let mut neumaier = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
let v = ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
PRECISION,
rug::Complex::parse("(1e-100,2e-100)").unwrap(),
))
.expect("valid test value");
neumaier.push(
ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(1.0, 2.0))
.unwrap(),
);
neumaier.push(v.clone());
neumaier.push(
ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(-1.0, -2.0))
.unwrap(),
);
assert_eq!(
neumaier.sum_before_compensation,
ComplexRugStrictFinite::<PRECISION>::zero()
);
assert_eq!(&neumaier.compensation, &v);
let sum = neumaier.result();
assert_eq!(sum, v);
println!("compensated sum = {}", sum);
}
#[test]
fn sum_big_values() {
let values = ["(1.0,2.0)", "(1e100,2e100)", "(1.0,2.0)", "(-1e100,-2e100)"]
.iter()
.map(|v| {
ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
PRECISION,
rug::Complex::parse(v).unwrap(),
))
.expect("valid test value")
})
.collect::<Vec<_>>();
let zero = ComplexRugStrictFinite::<PRECISION>::zero();
let sum = values.iter().fold(zero.clone(), |acc, x| acc + x);
assert_eq!(sum, zero);
let neumaier =
NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(
sum,
ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(2.0, 4.0))
.unwrap()
);
println!("compensated sum = {}", sum);
}
#[test]
fn sum_small_values() {
let values = ["(1.0,2.0)", "(1e-100,2e-100)", "(-1.0,-2.0)"]
.iter()
.map(|v| {
ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
PRECISION,
rug::Complex::parse(v).unwrap(),
))
.expect("valid test value")
})
.collect::<Vec<_>>();
let zero = ComplexRugStrictFinite::<PRECISION>::zero();
let sum = values.iter().fold(zero.clone(), |acc, x| acc + x);
assert_eq!(sum, zero);
let neumaier =
NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new_sequential(values);
let sum = neumaier.result();
assert_eq!(
sum,
ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
PRECISION,
rug::Complex::parse("(1e-100,2e-100)").unwrap(),
))
.expect("valid test value")
);
println!("compensated sum = {}", sum);
}
#[test]
fn combine_matches_new_sequential_for_chunks() {
let values = [
"(1.0,2.0)",
"(1e100,2e100)",
"(1.0,2.0)",
"(-1e100,-2e100)",
"(1e-100,2e-100)",
"(2.0,3.0)",
]
.iter()
.map(|v| {
ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
PRECISION,
rug::Complex::parse(v).unwrap(),
))
.expect("valid test value")
})
.collect::<Vec<_>>();
let mut chunk_1 = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
chunk_1.push(values[0].clone());
chunk_1.push(values[1].clone());
let mut chunk_2 = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
chunk_2.push(values[2].clone());
chunk_2.push(values[3].clone());
let mut chunk_3 = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
chunk_3.push(values[4].clone());
chunk_3.push(values[5].clone());
chunk_1.combine(chunk_2);
chunk_1.combine(chunk_3);
let sequential =
NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new_sequential(values);
assert_eq!(chunk_1.result(), sequential.result());
}
}
}
}
mod scaled_sum_sq_accumulator_tests {
use super::*;
use crate::ScalarCore;
const EPS: f64 = f64::EPSILON;
#[test]
fn single_value() {
let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc.push(NonNegativeRealScalar::new(3.0));
let result = acc.result_sqrt();
assert!((*result.as_raw_scalar_ref() - 3.0).abs() < EPS);
}
#[test]
fn three_four_five_sqrt() {
let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc.push(NonNegativeRealScalar::new(3.0));
acc.push(NonNegativeRealScalar::new(4.0));
let result = acc.result_sqrt();
assert!((*result.as_raw_scalar_ref() - 5.0).abs() < EPS);
}
#[test]
fn three_four_five_squared() {
let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc.push(NonNegativeRealScalar::new(3.0));
acc.push(NonNegativeRealScalar::new(4.0));
let result = acc.result();
assert!((*result.as_raw_scalar_ref() - 25.0).abs() < EPS);
}
#[test]
fn zero_vector_sqrt() {
let acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
let result = acc.result_sqrt();
assert_eq!(*result.as_raw_scalar_ref(), 0.0);
}
#[test]
fn zero_vector_squared() {
let acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
let result = acc.result();
assert_eq!(*result.as_raw_scalar_ref(), 0.0);
}
#[test]
fn negative_values() {
let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc.push(NonNegativeRealScalar::new(3.0));
acc.push(NonNegativeRealScalar::new(4.0));
let result = acc.result_sqrt();
assert!((*result.as_raw_scalar_ref() - 5.0).abs() < EPS);
}
#[test]
fn combine_other_scale_larger() {
let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc1.push(NonNegativeRealScalar::new(1.0));
acc1.push(NonNegativeRealScalar::new(1.0));
let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc2.push(NonNegativeRealScalar::new(100.0));
acc2.push(NonNegativeRealScalar::new(100.0));
acc1.combine(acc2);
let result = acc1.result_sqrt();
let expected = (1.0_f64 + 1.0 + 100.0 * 100.0 + 100.0 * 100.0).sqrt();
assert!((*result.as_raw_scalar_ref() - expected).abs() < EPS * expected);
}
#[test]
fn combine_self_scale_larger() {
let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc1.push(NonNegativeRealScalar::new(100.0));
acc1.push(NonNegativeRealScalar::new(100.0));
let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc2.push(NonNegativeRealScalar::new(1.0));
acc2.push(NonNegativeRealScalar::new(1.0));
acc1.combine(acc2);
let result = acc1.result_sqrt();
let expected = (100.0_f64 * 100.0 + 100.0 * 100.0 + 1.0 + 1.0).sqrt();
assert!((*result.as_raw_scalar_ref() - expected).abs() < EPS * expected);
}
#[test]
fn combine_both_zero() {
let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
let acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc1.combine(acc2);
let result = acc1.result_sqrt();
assert_eq!(*result.as_raw_scalar_ref(), 0.0);
}
#[test]
fn combine_one_zero() {
let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc2.push(NonNegativeRealScalar::new(3.0));
acc2.push(NonNegativeRealScalar::new(4.0));
acc1.combine(acc2);
let result = acc1.result_sqrt();
assert!((*result.as_raw_scalar_ref() - 5.0).abs() < EPS);
}
#[test]
fn combine_neumaier() {
let mut acc1 = ScaledSumSqAccumulator::<NeumaierSum<f64>>::new();
for _ in 0..100 {
acc1.push(NonNegativeRealScalar::new(0.01));
}
let mut acc2 = ScaledSumSqAccumulator::<NeumaierSum<f64>>::new();
acc2.push(NonNegativeRealScalar::new(10.0));
acc1.combine(acc2);
let result = acc1.result_sqrt();
let expected = (0.01_f64 + 100.0).sqrt();
assert!((*result.as_raw_scalar_ref() - expected).abs() < EPS * expected);
}
#[test]
fn result_vs_result_sqrt() {
let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc1.push(NonNegativeRealScalar::new(3.0));
acc1.push(NonNegativeRealScalar::new(4.0));
let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc2.push(NonNegativeRealScalar::new(3.0));
acc2.push(NonNegativeRealScalar::new(4.0));
let result = acc1.result();
let sqrt_result = acc2.result_sqrt();
let sqrt_squared = (*sqrt_result.as_raw_scalar_ref()).powi(2);
assert!((*result.as_raw_scalar_ref() - sqrt_squared).abs() < EPS);
}
#[test]
fn extreme_scale_difference() {
let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc1.push(NonNegativeRealScalar::new(1e-8));
let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
acc2.push(NonNegativeRealScalar::new(1e8));
acc1.combine(acc2);
let result = acc1.result_sqrt();
let expected = ((1e-8_f64).powi(2) + (1e8_f64).powi(2)).sqrt();
let rel_err = (*result.as_raw_scalar_ref() - expected).abs() / expected;
assert!(rel_err < 1e-10);
}
#[test]
fn sequential_vs_combined() {
let data = [1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut acc_seq = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
for &val in &data {
acc_seq.push(NonNegativeRealScalar::new(val));
}
let result_seq = acc_seq.result_sqrt();
let mut acc_chunk1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
for &val in &data[0..3] {
acc_chunk1.push(NonNegativeRealScalar::new(val));
}
let mut acc_chunk2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
for &val in &data[3..] {
acc_chunk2.push(NonNegativeRealScalar::new(val));
}
acc_chunk1.combine(acc_chunk2);
let result_combined = acc_chunk1.result_sqrt();
assert!(
(*result_seq.as_raw_scalar_ref() - *result_combined.as_raw_scalar_ref()).abs()
< EPS
);
}
}
}