1#![deny(rustdoc::broken_intra_doc_links)]
2
3use crate::{
39 FpScalar, RealScalar,
40 core::traits::validation::sealed,
41 functions::{Abs, Pow},
42 scalars::NonNegativeRealScalar,
43};
44use getset::Getters;
45use num::{Complex, One, Zero};
46use rayon::prelude::*;
47use std::ops::{Add, AddAssign, Mul, MulAssign};
48
49pub trait Accumulator: Sized {
64 type Input;
66 type Output;
68
69 fn new() -> Self;
71
72 fn push(&mut self, value: Self::Input);
74
75 fn combine(&mut self, other: Self);
81
82 fn result(self) -> Self::Output;
84
85 fn new_sequential<I>(values: I) -> Self
104 where
105 I: IntoIterator<Item = Self::Input>,
106 {
107 let mut acc = Self::new();
108 values.into_iter().for_each(|v| acc.push(v));
109 acc
110 }
111
112 fn new_parallel<I>(values: I) -> Self
134 where
135 Self: Send,
136 I: IntoParallelIterator<Item = Self::Input>,
137 {
138 values
139 .into_par_iter()
140 .fold(
141 || Self::new(),
142 |mut acc, item| {
143 acc.push(item);
144 acc
145 },
146 )
147 .reduce(
148 || Self::new(),
149 |mut a, b| {
150 a.combine(b);
151 a
152 },
153 )
154 }
155}
156pub trait SumAccumulator:
176 Accumulator<Input: Zero + Clone, Output = <Self as Accumulator>::Input>
177{
178 fn rescale_by(&mut self, r: &Self::Input);
180}
181pub struct NaiveSum<T>(T);
208
209impl<T> Accumulator for NaiveSum<T>
210where
211 T: Zero + Clone + for<'a> Add<&'a T, Output = T> + for<'a> Mul<&'a T, Output = T> + AddAssign,
212{
213 type Input = T;
214 type Output = T;
215
216 #[inline(always)]
217 fn new() -> Self {
218 NaiveSum(T::zero())
219 }
220
221 #[inline(always)]
222 fn push(&mut self, r: T) {
223 self.0 += r;
224 }
225
226 #[inline(always)]
227 fn result(self) -> T {
228 self.0
229 }
230
231 #[inline(always)]
232 fn combine(&mut self, other: Self) {
233 self.0 += other.0;
234 }
235}
236
237impl<T> SumAccumulator for NaiveSum<T>
238where
239 T: Zero
240 + Clone
241 + for<'a> Add<&'a T, Output = T>
242 + for<'a> Mul<&'a T, Output = T>
243 + AddAssign
244 + for<'a> MulAssign<&'a T>,
245{
246 #[inline(always)]
247 fn rescale_by(&mut self, r: &T) {
248 self.0 *= r;
249 }
250}
251#[inline(always)]
255fn neumaier_sum_and_compensation_native_real<T>(value: T, sum: &mut T, compensation: &mut T)
256where
257 T: sealed::NativeReal + AddAssign,
258{
259 let sum_before_compensation = *sum;
260 *sum += value; *compensation += if sum_before_compensation.abs() >= value.abs() {
264 (sum_before_compensation - *sum) + value
266 } else {
267 (value - *sum) + sum_before_compensation
269 };
270}
271
272#[inline(always)]
273pub(crate) fn neumaier_sum_and_compensation_real<RealType>(
274 value: RealType,
275 sum: &mut RealType,
276 compensation: &mut RealType,
277) where
278 RealType: RealScalar,
279{
280 let sum_before_compensation = sum.clone();
281 *sum += &value; *compensation += if sum_before_compensation.clone().abs() >= value.clone().abs() {
285 (sum_before_compensation - &*sum) + value
287 } else {
288 (value - &*sum) + sum_before_compensation
290 };
291}
292
293pub trait NeumaierAddable: Sized {
305 fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self);
317}
318
319impl<T> NeumaierAddable for T
320where
321 T: sealed::NativeReal + AddAssign,
322{
323 #[inline(always)]
324 fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
325 neumaier_sum_and_compensation_native_real(value, sum, compensation)
326 }
327}
328
329impl<T> NeumaierAddable for Complex<T>
330where
331 T: sealed::NativeReal + AddAssign,
332{
333 #[inline(always)]
334 fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
335 neumaier_sum_and_compensation_native_real(value.re, &mut sum.re, &mut compensation.re);
336 neumaier_sum_and_compensation_native_real(value.im, &mut sum.im, &mut compensation.im);
337 }
338}
339
340#[cfg(feature = "rug")]
341mod rug_impls {
342 use super::*;
343
344 #[inline(always)]
345 fn neumaier_sum_and_compensation_rug_float(
346 value: rug::Float,
347 sum: &mut rug::Float,
348 compensation: &mut rug::Float,
349 ) {
350 let sum_before_compensation = sum.clone();
351 *sum += &value;
352
353 *compensation += if sum_before_compensation.clone().abs() >= value.clone().abs() {
355 (sum_before_compensation - &*sum) + value
357 } else {
358 (value - &*sum) + sum_before_compensation
360 };
361 }
362
363 impl NeumaierAddable for rug::Float {
364 #[inline(always)]
365 fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
366 neumaier_sum_and_compensation_rug_float(value, sum, compensation)
367 }
368 }
369
370 impl NeumaierAddable for rug::Complex {
371 #[inline(always)]
372 fn neumaier_compensated_sum(value: Self, sum: &mut Self, compensation: &mut Self) {
373 let (value_real, value_imag) = value.into_real_imag();
374
375 neumaier_sum_and_compensation_rug_float(
376 value_real,
377 sum.mut_real(),
378 compensation.mut_real(),
379 );
380
381 neumaier_sum_and_compensation_rug_float(
382 value_imag,
383 sum.mut_imag(),
384 compensation.mut_imag(),
385 );
386 }
387 }
388}
389
390#[derive(Debug, Clone, Getters)]
427pub struct NeumaierSum<ScalarType> {
428 #[getset(get = "pub")]
430 sum_before_compensation: ScalarType,
431
432 #[getset(get = "pub")]
435 compensation: ScalarType,
436}
437
438impl<ScalarType> Accumulator for NeumaierSum<ScalarType>
439where
440 ScalarType: Clone
441 + Zero
442 + for<'a> Add<&'a ScalarType, Output = ScalarType>
443 + for<'a> AddAssign<&'a ScalarType>
444 + for<'a> Mul<&'a ScalarType, Output = ScalarType>
445 + for<'a> MulAssign<&'a ScalarType>
446 + NeumaierAddable,
447{
448 type Input = ScalarType;
449 type Output = ScalarType;
450
451 #[inline(always)]
452 fn new() -> Self {
453 Self {
454 sum_before_compensation: ScalarType::zero(),
455 compensation: ScalarType::zero(),
456 }
457 }
458
459 #[inline(always)]
460 fn push(&mut self, value: Self::Input) {
461 <ScalarType as NeumaierAddable>::neumaier_compensated_sum(
462 value,
463 &mut self.sum_before_compensation,
464 &mut self.compensation,
465 );
466 }
467
468 #[inline(always)]
469 fn result(self) -> Self::Input {
470 self.sum_before_compensation + self.compensation
471 }
472
473 #[inline(always)]
474 fn combine(&mut self, other: Self) {
475 self.push(other.sum_before_compensation);
476 self.push(other.compensation);
477 }
478}
479
480impl<ScalarType> SumAccumulator for NeumaierSum<ScalarType>
481where
482 ScalarType: Clone
483 + Zero
484 + for<'a> Add<&'a ScalarType, Output = ScalarType>
485 + for<'a> AddAssign<&'a ScalarType>
486 + for<'a> Mul<&'a ScalarType, Output = ScalarType>
487 + for<'a> MulAssign<&'a ScalarType>
488 + NeumaierAddable,
489{
490 #[inline(always)]
491 fn rescale_by(&mut self, r: &Self::Input) {
492 self.sum_before_compensation += &self.compensation;
494
495 self.sum_before_compensation *= r;
497 self.compensation = ScalarType::zero();
498 }
499}
500pub struct MaxAccumulator<RealType: RealScalar> {
515 max_value: RealType,
516}
517
518impl<RealType: RealScalar> Accumulator for MaxAccumulator<RealType> {
519 type Input = RealType;
520 type Output = RealType;
521
522 #[inline(always)]
523 fn new() -> Self {
524 Self {
525 max_value: RealType::min_finite(),
526 }
527 }
528
529 #[inline(always)]
530 fn push(&mut self, value: Self::Input) {
531 if value > self.max_value {
532 self.max_value = value;
533 }
534 }
535
536 #[inline(always)]
537 fn result(self) -> Self::Output {
538 self.max_value
539 }
540
541 #[inline(always)]
542 fn combine(&mut self, other: Self) {
543 if other.max_value > self.max_value {
544 self.max_value = other.max_value;
545 }
546 }
547}
548
549pub struct MinAccumulator<RealType: RealScalar> {
555 min_value: RealType,
556}
557
558impl<RealType: RealScalar> Accumulator for MinAccumulator<RealType> {
559 type Input = RealType;
560 type Output = RealType;
561
562 #[inline(always)]
563 fn new() -> Self {
564 Self {
565 min_value: RealType::max_finite(),
566 }
567 }
568
569 #[inline(always)]
570 fn push(&mut self, value: Self::Input) {
571 if value < self.min_value {
572 self.min_value = value;
573 }
574 }
575
576 #[inline(always)]
577 fn result(self) -> Self::Output {
578 self.min_value
579 }
580
581 #[inline(always)]
582 fn combine(&mut self, other: Self) {
583 if other.min_value < self.min_value {
584 self.min_value = other.min_value;
585 }
586 }
587}
588pub struct MaxAbsValueAccumulator<ScalarType: FpScalar> {
605 max_abs: NonNegativeRealScalar<ScalarType::RealType>, }
607
608impl<ScalarType: FpScalar> Accumulator for MaxAbsValueAccumulator<ScalarType> {
609 type Input = ScalarType;
610 type Output = NonNegativeRealScalar<ScalarType::RealType>;
611
612 fn new() -> Self {
613 Self {
614 max_abs: NonNegativeRealScalar::zero(),
615 }
616 }
617
618 fn push(&mut self, value: ScalarType) {
619 let abs_v = value.abs();
620 if abs_v > self.max_abs {
621 self.max_abs = abs_v;
622 }
623 }
624
625 fn combine(&mut self, other: Self) {
626 if other.max_abs > self.max_abs {
627 self.max_abs = other.max_abs;
628 }
629 }
630
631 fn result(self) -> NonNegativeRealScalar<ScalarType::RealType> {
632 self.max_abs
633 }
634}
635pub struct ScaledSumSqAccumulator<SumSqAcc>
672where
673 SumSqAcc: SumAccumulator<Input: RealScalar>,
674{
675 scale: NonNegativeRealScalar<SumSqAcc::Input>,
677 sumsq_accumulator: SumSqAcc,
679 zero: NonNegativeRealScalar<SumSqAcc::Input>,
680}
681
682impl<SumSqAcc> ScaledSumSqAccumulator<SumSqAcc>
683where
684 SumSqAcc: SumAccumulator<Input: RealScalar>,
685{
686 fn add_value_and_update_scale(&mut self, value: NonNegativeRealScalar<SumSqAcc::Input>) {
687 if value > self.zero {
688 let r_sq = if self.scale < value {
689 if self.scale > self.zero {
690 let r = std::mem::replace(&mut self.scale, value).into_inner()
698 / self.scale.as_ref();
699
700 self.sumsq_accumulator.rescale_by(&r.pow(2));
701 } else {
702 self.scale = value;
703 }
704 SumSqAcc::Input::one()
705 } else {
706 (value.into_inner() / self.scale.as_ref()).pow(2)
707 };
708 self.sumsq_accumulator.push(r_sq);
709 }
710 }
711
712 pub fn result_sqrt(self) -> NonNegativeRealScalar<SumSqAcc::Input> {
713 if self.scale == self.zero {
714 self.zero
715 } else {
716 NonNegativeRealScalar::new(self.sumsq_accumulator.result()).sqrt() * self.scale
717 }
718 }
719
720 pub fn new_sequential<I>(values: I) -> Self
726 where
727 I: IntoIterator<Item = NonNegativeRealScalar<SumSqAcc::Input>>,
728 {
729 let mut acc = Self::new();
730 values.into_iter().for_each(|v| acc.push(v));
731 acc
732 }
733
734 pub fn new_sequential_from_scalars<I>(values: I) -> Self
751 where
752 I: IntoIterator<Item: FpScalar<RealType = SumSqAcc::Input>>,
753 {
754 let mut acc = Self::new();
755 values.into_iter().for_each(|v| {
756 acc.push(v.abs());
757 });
758 acc
759 }
760
761 pub fn new_parallel_from_scalars<I>(values: I) -> Self
766 where
767 Self: Send,
768 I: IntoParallelIterator<Item: FpScalar<RealType = SumSqAcc::Input>>,
769 {
770 values
771 .into_par_iter()
772 .fold(Self::new, |mut acc, item| {
773 acc.push(item.abs());
774 acc
775 })
776 .reduce(Self::new, |mut a, b| {
777 a.combine(b);
778 a
779 })
780 }
781}
782
783impl<SumSqAcc> Accumulator for ScaledSumSqAccumulator<SumSqAcc>
784where
785 SumSqAcc: SumAccumulator<Input: RealScalar>,
786{
787 type Input = NonNegativeRealScalar<SumSqAcc::Input>;
789 type Output = NonNegativeRealScalar<SumSqAcc::Input>;
790
791 fn new() -> Self {
792 Self {
793 scale: NonNegativeRealScalar::<SumSqAcc::Input>::zero(),
794 sumsq_accumulator: SumSqAcc::new(),
795 zero: NonNegativeRealScalar::<SumSqAcc::Input>::zero(),
796 }
797 }
798
799 fn push(&mut self, value: Self::Input) {
802 self.add_value_and_update_scale(value);
803 }
804
805 fn combine(&mut self, mut other: Self) {
808 if other.scale > self.scale {
809 if self.scale > self.zero {
810 let r = std::mem::replace(&mut self.scale, other.scale).into_inner()
814 / self.scale.as_ref();
815 self.sumsq_accumulator.rescale_by(&r.pow(2));
818 } else {
819 self.scale = other.scale;
820 }
821 self.sumsq_accumulator.combine(other.sumsq_accumulator);
822 } else if other.scale > self.zero {
823 if other.scale < self.scale {
824 let r = other.scale.into_inner() / self.scale.as_ref();
825 other.sumsq_accumulator.rescale_by(&r.pow(2));
826 }
827 self.sumsq_accumulator.combine(other.sumsq_accumulator);
828 }
829 }
831
832 fn result(self) -> Self::Output {
834 if self.scale == self.zero {
835 self.zero
836 } else {
837 NonNegativeRealScalar::new(
838 self.sumsq_accumulator.result() * self.scale.into_inner().pow(2),
839 )
840 }
841 }
842}
843#[cfg(test)]
847mod tests {
848 use super::*;
849
850 mod neumaier_sum {
851 use super::*;
852
853 mod native {
854 use super::*;
855
856 mod real {
857 use super::*;
858
859 #[test]
860 fn new() {
861 let neumaier = NeumaierSum::<f64>::new();
862 assert_eq!(neumaier.sum_before_compensation, 0.0);
863 assert_eq!(neumaier.compensation, 0.0);
864 }
865
866 #[test]
867 fn add() {
868 let mut neumaier = NeumaierSum::<f64>::new();
869 neumaier.push(1.0);
870 neumaier.push(1e-16);
871 neumaier.push(-1.0);
872 assert_eq!(neumaier.sum_before_compensation, 0.0);
873 assert_eq!(neumaier.compensation, 1e-16);
874 }
875
876 #[test]
877 fn sum() {
878 let mut neumaier = NeumaierSum::<f64>::new();
879 neumaier.push(1.0);
880 neumaier.push(1e-16);
881 neumaier.push(-1.0);
882 assert_eq!(neumaier.sum_before_compensation, 0.0);
883 assert_eq!(neumaier.compensation, 1e-16);
884 let sum = neumaier.result();
885 assert_eq!(sum, 1e-16);
886 println!("compensated sum = {}", sum);
887 }
888
889 #[test]
890 fn sum_big_values() {
891 let values = vec![1.0, 1e100, 1.0, -1e100];
892 let sum = values.iter().sum::<f64>();
893 assert_eq!(sum, 0.0);
894
895 let neumaier = NeumaierSum::<f64>::new_sequential(values);
896 let sum = neumaier.result();
897 assert_eq!(sum, 2.0);
898 println!("compensated sum = {}", sum);
899 }
900
901 #[test]
902 fn sum_small_values() {
903 let values = [1.0, 1e-100, -1.0];
904 let sum = values.iter().sum::<f64>();
905 assert_eq!(sum, 0.0);
906
907 let neumaier = NeumaierSum::<f64>::new_sequential(values);
908 let sum = neumaier.result();
909 assert_eq!(sum, 1e-100);
910 println!("compensated sum = {}", sum);
911 }
912
913 #[test]
914 fn combine_partial_sums() {
915 let mut left = NaiveSum::<f64>::new();
916 left.push(1.0);
917 left.push(2.0);
918
919 let mut right = NaiveSum::<f64>::new();
920 right.push(3.0);
921 right.push(4.0);
922
923 left.combine(right);
924 assert_eq!(left.result(), 10.0);
925 }
926
927 #[test]
928 fn combine_partial_neumaier_sums() {
929 let mut left = NeumaierSum::<f64>::new();
930 left.push(1.0);
931 left.push(1e-16);
932
933 let mut right = NeumaierSum::<f64>::new();
934 right.push(2.0);
935 right.push(1e-16);
936
937 left.combine(right);
938 let sum = left.result();
939 assert!((sum - (3.0 + 2e-16)).abs() < 1e-15);
940 }
941
942 #[test]
943 fn combine_matches_new_sequential_for_chunks() {
944 let values = [1.0_f64, 1e100, 1.0, -1e100, 1e-16, 2.0];
945
946 let mut chunk_1 = NaiveSum::<f64>::new();
947 chunk_1.push(values[0]);
948 chunk_1.push(values[1]);
949
950 let mut chunk_2 = NaiveSum::<f64>::new();
951 chunk_2.push(values[2]);
952 chunk_2.push(values[3]);
953
954 let mut chunk_3 = NaiveSum::<f64>::new();
955 chunk_3.push(values[4]);
956 chunk_3.push(values[5]);
957
958 chunk_1.combine(chunk_2);
959 chunk_1.combine(chunk_3);
960
961 let sequential = NaiveSum::<f64>::new_sequential(values);
962 assert_eq!(chunk_1.result(), sequential.result());
963 }
964
965 #[test]
966 fn combine_is_order_dependent_for_naive_sum() {
967 let mut left_grouped = NaiveSum::<f64>::new();
968 left_grouped.push(1e16);
969
970 let mut middle = NaiveSum::<f64>::new();
971 middle.push(1.0);
972
973 let mut right_last = NaiveSum::<f64>::new();
974 right_last.push(-1e16);
975
976 left_grouped.combine(middle);
977 left_grouped.combine(right_last);
978 let left_result = left_grouped.result();
979
980 let mut right_grouped = NaiveSum::<f64>::new();
981 right_grouped.push(1e16);
982
983 let mut right_last = NaiveSum::<f64>::new();
984 right_last.push(-1e16);
985
986 let mut middle = NaiveSum::<f64>::new();
987 middle.push(1.0);
988
989 right_grouped.combine(right_last);
990 right_grouped.combine(middle);
991 let right_result = right_grouped.result();
992
993 assert_eq!(left_result, 0.0);
994 assert_eq!(right_result, 1.0);
995 assert_ne!(left_result, right_result);
996 }
997
998 #[test]
999 fn neumaier_combine_matches_new_sequential_for_chunks() {
1000 let values = [1.0_f64, 1e100, 1.0, -1e100, 1e-16, 2.0];
1001
1002 let mut chunk_1 = NeumaierSum::<f64>::new();
1003 chunk_1.push(values[0]);
1004 chunk_1.push(values[1]);
1005
1006 let mut chunk_2 = NeumaierSum::<f64>::new();
1007 chunk_2.push(values[2]);
1008 chunk_2.push(values[3]);
1009
1010 let mut chunk_3 = NeumaierSum::<f64>::new();
1011 chunk_3.push(values[4]);
1012 chunk_3.push(values[5]);
1013
1014 chunk_1.combine(chunk_2);
1015 chunk_1.combine(chunk_3);
1016
1017 let sequential = NeumaierSum::<f64>::new_sequential(values);
1018 assert_eq!(chunk_1.result(), sequential.result());
1019 }
1020
1021 #[test]
1022 fn neumaier_combine_is_stable_across_orders() {
1023 let mut left_order = NeumaierSum::<f64>::new();
1024 left_order.push(1e16);
1025
1026 let mut middle = NeumaierSum::<f64>::new();
1027 middle.push(1.0);
1028
1029 let mut right_last = NeumaierSum::<f64>::new();
1030 right_last.push(-1e16);
1031
1032 left_order.combine(middle);
1033 left_order.combine(right_last);
1034 let left_result = left_order.result();
1035
1036 let mut right_order = NeumaierSum::<f64>::new();
1037 right_order.push(1e16);
1038
1039 let mut right_last = NeumaierSum::<f64>::new();
1040 right_last.push(-1e16);
1041
1042 let mut middle = NeumaierSum::<f64>::new();
1043 middle.push(1.0);
1044
1045 right_order.combine(right_last);
1046 right_order.combine(middle);
1047 let right_result = right_order.result();
1048
1049 assert_eq!(left_result, 1.0);
1050 assert_eq!(right_result, 1.0);
1051 assert_eq!(left_result, right_result);
1052 }
1053 }
1054
1055 mod complex {
1056 use super::*;
1057 use num::Complex;
1058
1059 #[test]
1060 fn new() {
1061 let neumaier = NeumaierSum::<Complex<f64>>::new();
1062
1063 let zero = Complex::new(0.0, 0.0);
1064 assert_eq!(&neumaier.sum_before_compensation, &zero);
1065 assert_eq!(&neumaier.compensation, &zero);
1066 }
1067
1068 #[test]
1069 fn add() {
1070 let mut neumaier = NeumaierSum::<Complex<f64>>::new();
1071
1072 let zero = Complex::new(0.0, 0.0);
1073 let v = Complex::new(1e-16, 2e-16);
1074
1075 neumaier.push(Complex::new(1.0, 2.0));
1076 neumaier.push(v);
1077 neumaier.push(Complex::new(-1.0, -2.0));
1078
1079 assert_eq!(neumaier.sum_before_compensation, zero);
1080 assert_eq!(neumaier.compensation, v);
1081 }
1082
1083 #[test]
1084 fn sum() {
1085 let mut neumaier = NeumaierSum::<Complex<f64>>::new();
1086
1087 let zero = Complex::new(0.0, 0.0);
1088 let v = Complex::new(1e-16, 2e-16);
1089
1090 neumaier.push(Complex::new(1.0, 2.0));
1091 neumaier.push(v);
1092 neumaier.push(Complex::new(-1.0, -2.0));
1093 assert_eq!(neumaier.sum_before_compensation, zero);
1094 assert_eq!(neumaier.compensation, v);
1095 let sum = neumaier.result();
1096 assert_eq!(sum, v);
1097 println!("compensated sum = {}", sum);
1098 }
1099
1100 #[test]
1101 fn sum_big_values() {
1102 let values = vec![
1103 Complex::new(1.0, 2.0),
1104 Complex::new(1e100, 2e100),
1105 Complex::new(1.0, 2.0),
1106 Complex::new(-1e100, -2e100),
1107 ];
1108 let sum = values.iter().sum::<Complex<f64>>();
1109 assert_eq!(sum, Complex::new(0.0, 0.0));
1110
1111 let neumaier = NeumaierSum::<Complex<f64>>::new_sequential(values);
1112 let sum = neumaier.result();
1113 assert_eq!(sum, Complex::new(2.0, 4.0));
1114 println!("compensated sum = {}", sum);
1115 }
1116
1117 #[test]
1118 fn sum_small_values() {
1119 let v = Complex::new(1e-100, 2e-100);
1120
1121 let values = [Complex::new(1.0, 2.0), v, Complex::new(-1.0, -2.0)];
1122 let sum = values.iter().sum::<Complex<f64>>();
1123 assert_eq!(sum, Complex::new(0.0, 0.0));
1124
1125 let neumaier = NeumaierSum::<Complex<f64>>::new_sequential(values);
1126 let sum = neumaier.result();
1127 assert_eq!(sum, v);
1128 println!("compensated sum = {}", sum);
1129 }
1130
1131 #[test]
1132 fn combine_partial_sums() {
1133 let mut left = NaiveSum::<Complex<f64>>::new();
1134 left.push(Complex::new(1.0, 2.0));
1135
1136 let mut right = NaiveSum::<Complex<f64>>::new();
1137 right.push(Complex::new(3.0, 4.0));
1138
1139 left.combine(right);
1140 assert_eq!(left.result(), Complex::new(4.0, 6.0));
1141 }
1142
1143 #[test]
1144 fn combine_partial_neumaier_sums() {
1145 let mut left = NeumaierSum::<Complex<f64>>::new();
1146 left.push(Complex::new(1.0, 2.0));
1147 left.push(Complex::new(1e-16, 2e-16));
1148
1149 let mut right = NeumaierSum::<Complex<f64>>::new();
1150 right.push(Complex::new(3.0, 4.0));
1151 right.push(Complex::new(1e-16, 2e-16));
1152
1153 left.combine(right);
1154 assert_eq!(
1155 left.result(),
1156 Complex::new(4.0, 6.0) + Complex::new(2e-16, 4e-16)
1157 );
1158 }
1159
1160 #[test]
1161 fn combine_matches_new_sequential_for_chunks() {
1162 let values = [
1163 Complex::new(1.0, 2.0),
1164 Complex::new(1e100, 2e100),
1165 Complex::new(1.0, 2.0),
1166 Complex::new(-1e100, -2e100),
1167 Complex::new(1e-16, 2e-16),
1168 Complex::new(2.0, 3.0),
1169 ];
1170
1171 let mut chunk_1 = NaiveSum::<Complex<f64>>::new();
1172 chunk_1.push(values[0]);
1173 chunk_1.push(values[1]);
1174
1175 let mut chunk_2 = NaiveSum::<Complex<f64>>::new();
1176 chunk_2.push(values[2]);
1177 chunk_2.push(values[3]);
1178
1179 let mut chunk_3 = NaiveSum::<Complex<f64>>::new();
1180 chunk_3.push(values[4]);
1181 chunk_3.push(values[5]);
1182
1183 chunk_1.combine(chunk_2);
1184 chunk_1.combine(chunk_3);
1185
1186 let sequential = NaiveSum::<Complex<f64>>::new_sequential(values);
1187 assert_eq!(chunk_1.result(), sequential.result());
1188 }
1189
1190 #[test]
1191 fn neumaier_combine_matches_new_sequential_for_chunks() {
1192 let values = [
1193 Complex::new(1.0, 2.0),
1194 Complex::new(1e100, 2e100),
1195 Complex::new(1.0, 2.0),
1196 Complex::new(-1e100, -2e100),
1197 Complex::new(1e-16, 2e-16),
1198 Complex::new(2.0, 3.0),
1199 ];
1200
1201 let mut chunk_1 = NeumaierSum::<Complex<f64>>::new();
1202 chunk_1.push(values[0]);
1203 chunk_1.push(values[1]);
1204
1205 let mut chunk_2 = NeumaierSum::<Complex<f64>>::new();
1206 chunk_2.push(values[2]);
1207 chunk_2.push(values[3]);
1208
1209 let mut chunk_3 = NeumaierSum::<Complex<f64>>::new();
1210 chunk_3.push(values[4]);
1211 chunk_3.push(values[5]);
1212
1213 chunk_1.combine(chunk_2);
1214 chunk_1.combine(chunk_3);
1215
1216 let sequential = NeumaierSum::<Complex<f64>>::new_sequential(values);
1217 assert_eq!(chunk_1.result(), sequential.result());
1218 }
1219 }
1220 }
1221
1222 #[cfg(feature = "rug")]
1223 mod rug100 {
1224 use super::*;
1225 use crate::{ComplexRugStrictFinite, RealRugStrictFinite};
1226 use try_create::TryNew;
1227
1228 const PRECISION: u32 = 100;
1229
1230 mod real {
1231 use super::*;
1232 use crate::RealScalar;
1233
1234 #[test]
1235 fn new() {
1236 let neumaier = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1237 assert!(neumaier.sum_before_compensation.is_zero());
1238 assert!(neumaier.compensation.is_zero());
1239 }
1240
1241 #[test]
1242 fn add() {
1243 let mut neumaier = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1244
1245 let v = RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
1246 PRECISION,
1247 rug::Float::parse("1e-100").unwrap(),
1248 ))
1249 .expect("valid test value");
1250
1251 neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
1252 neumaier.push(v.clone());
1253 neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1.0).unwrap());
1254
1255 assert_eq!(
1256 neumaier.sum_before_compensation,
1257 RealRugStrictFinite::<PRECISION>::try_from_f64(0.0).unwrap()
1258 );
1259 assert_eq!(&neumaier.compensation, &v);
1260 }
1261
1262 #[test]
1263 fn sum() {
1264 let mut neumaier = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1265
1266 let v = RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
1267 PRECISION,
1268 rug::Float::parse("1e-100").unwrap(),
1269 ))
1270 .expect("valid test value");
1271
1272 neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
1273 neumaier.push(v.clone());
1274 neumaier.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1.0).unwrap());
1275
1276 assert!(neumaier.sum_before_compensation.is_zero());
1277 assert_eq!(&neumaier.compensation, &v);
1278 let sum = neumaier.result();
1279 assert_eq!(sum, v);
1280 println!("compensated sum = {}", sum);
1281 }
1282
1283 #[test]
1284 fn sum_big_values() {
1285 let values = ["1.0", "1e100", "1.0", "-1e100"]
1286 .iter()
1287 .map(|v| {
1288 RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
1289 PRECISION,
1290 rug::Float::parse(v).unwrap(),
1291 ))
1292 .expect("valid test value")
1293 })
1294 .collect::<Vec<_>>();
1295
1296 let sum = values
1297 .iter()
1298 .fold(RealRugStrictFinite::<PRECISION>::zero(), |acc, x| acc + x);
1299 assert!(sum.is_zero());
1300
1301 let neumaier =
1302 NeumaierSum::<RealRugStrictFinite<PRECISION>>::new_sequential(values);
1303 let sum = neumaier.result();
1304 assert_eq!(
1305 sum,
1306 RealRugStrictFinite::<PRECISION>::try_from_f64(2.0).unwrap()
1307 );
1308 println!("compensated sum = {}", sum);
1309 }
1310
1311 #[test]
1312 fn sum_small_values() {
1313 let values = ["1.0", "1e-100", "-1.0"]
1314 .iter()
1315 .map(|v| {
1316 RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
1317 PRECISION,
1318 rug::Float::parse(v).unwrap(),
1319 ))
1320 .expect("valid test value")
1321 })
1322 .collect::<Vec<_>>();
1323
1324 let sum = values
1325 .iter()
1326 .fold(RealRugStrictFinite::<PRECISION>::zero(), |acc, x| acc + x);
1327 assert_eq!(sum, RealRugStrictFinite::<PRECISION>::zero());
1328
1329 let neumaier =
1330 NeumaierSum::<RealRugStrictFinite<PRECISION>>::new_sequential(values);
1331 let sum = neumaier.result();
1332 assert_eq!(
1333 sum,
1334 RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
1335 PRECISION,
1336 rug::Float::parse("1e-100").unwrap(),
1337 ))
1338 .expect("valid test value")
1339 );
1340 println!("compensated sum = {}", sum);
1341 }
1342
1343 #[test]
1344 fn combine_matches_new_sequential_for_chunks() {
1345 let values = ["1.0", "1e100", "1.0", "-1e100", "1e-100", "2.0"]
1346 .iter()
1347 .map(|v| {
1348 RealRugStrictFinite::<PRECISION>::try_new(rug::Float::with_val(
1349 PRECISION,
1350 rug::Float::parse(v).unwrap(),
1351 ))
1352 .expect("valid test value")
1353 })
1354 .collect::<Vec<_>>();
1355
1356 let mut chunk_1 = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1357 chunk_1.push(values[0].clone());
1358 chunk_1.push(values[1].clone());
1359
1360 let mut chunk_2 = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1361 chunk_2.push(values[2].clone());
1362 chunk_2.push(values[3].clone());
1363
1364 let mut chunk_3 = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1365 chunk_3.push(values[4].clone());
1366 chunk_3.push(values[5].clone());
1367
1368 chunk_1.combine(chunk_2);
1369 chunk_1.combine(chunk_3);
1370
1371 let sequential =
1372 NeumaierSum::<RealRugStrictFinite<PRECISION>>::new_sequential(values);
1373 assert_eq!(chunk_1.result(), sequential.result());
1374 }
1375
1376 #[test]
1377 fn neumaier_combine_is_stable_across_orders() {
1378 let mut left_order = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1379 left_order.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1e16).unwrap());
1380
1381 let mut middle = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1382 middle.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
1383
1384 let mut right_last = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1385 right_last.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1e16).unwrap());
1386
1387 left_order.combine(middle);
1388 left_order.combine(right_last);
1389 let left_result = left_order.result();
1390
1391 let mut right_order = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1392 right_order.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1e16).unwrap());
1393
1394 let mut right_last = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1395 right_last.push(RealRugStrictFinite::<PRECISION>::try_from_f64(-1e16).unwrap());
1396
1397 let mut middle = NeumaierSum::<RealRugStrictFinite<PRECISION>>::new();
1398 middle.push(RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap());
1399
1400 right_order.combine(right_last);
1401 right_order.combine(middle);
1402 let right_result = right_order.result();
1403
1404 assert_eq!(
1405 left_result,
1406 RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap()
1407 );
1408 assert_eq!(
1409 right_result,
1410 RealRugStrictFinite::<PRECISION>::try_from_f64(1.0).unwrap()
1411 );
1412 assert_eq!(left_result, right_result);
1413 }
1414 }
1415
1416 mod complex {
1417 use super::*;
1418
1419 #[test]
1420 fn new() {
1421 let neumaier = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
1422 assert_eq!(
1423 neumaier.sum_before_compensation,
1424 ComplexRugStrictFinite::<PRECISION>::zero()
1425 );
1426 assert_eq!(
1427 neumaier.compensation,
1428 ComplexRugStrictFinite::<PRECISION>::zero()
1429 );
1430 }
1431
1432 #[test]
1433 fn add() {
1434 let mut neumaier = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
1435
1436 let v = ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
1437 PRECISION,
1438 rug::Complex::parse("(1e-100,2e-100)").unwrap(),
1439 ))
1440 .expect("valid test value");
1441
1442 neumaier.push(
1443 ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(1.0, 2.0))
1444 .unwrap(),
1445 );
1446 neumaier.push(v.clone());
1447 neumaier.push(
1448 ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(-1.0, -2.0))
1449 .unwrap(),
1450 );
1451
1452 assert_eq!(
1453 neumaier.sum_before_compensation,
1454 ComplexRugStrictFinite::<PRECISION>::zero()
1455 );
1456 assert_eq!(&neumaier.compensation, &v);
1457 }
1458
1459 #[test]
1460 fn sum() {
1461 let mut neumaier = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
1462
1463 let v = ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
1464 PRECISION,
1465 rug::Complex::parse("(1e-100,2e-100)").unwrap(),
1466 ))
1467 .expect("valid test value");
1468
1469 neumaier.push(
1470 ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(1.0, 2.0))
1471 .unwrap(),
1472 );
1473 neumaier.push(v.clone());
1474 neumaier.push(
1475 ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(-1.0, -2.0))
1476 .unwrap(),
1477 );
1478
1479 assert_eq!(
1480 neumaier.sum_before_compensation,
1481 ComplexRugStrictFinite::<PRECISION>::zero()
1482 );
1483 assert_eq!(&neumaier.compensation, &v);
1484 let sum = neumaier.result();
1485 assert_eq!(sum, v);
1486 println!("compensated sum = {}", sum);
1487 }
1488
1489 #[test]
1490 fn sum_big_values() {
1491 let values = ["(1.0,2.0)", "(1e100,2e100)", "(1.0,2.0)", "(-1e100,-2e100)"]
1492 .iter()
1493 .map(|v| {
1494 ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
1495 PRECISION,
1496 rug::Complex::parse(v).unwrap(),
1497 ))
1498 .expect("valid test value")
1499 })
1500 .collect::<Vec<_>>();
1501
1502 let zero = ComplexRugStrictFinite::<PRECISION>::zero();
1503 let sum = values.iter().fold(zero.clone(), |acc, x| acc + x);
1504 assert_eq!(sum, zero);
1505
1506 let neumaier =
1507 NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new_sequential(values);
1508 let sum = neumaier.result();
1509 assert_eq!(
1510 sum,
1511 ComplexRugStrictFinite::<PRECISION>::try_from(Complex::new(2.0, 4.0))
1512 .unwrap()
1513 );
1514 println!("compensated sum = {}", sum);
1515 }
1516
1517 #[test]
1518 fn sum_small_values() {
1519 let values = ["(1.0,2.0)", "(1e-100,2e-100)", "(-1.0,-2.0)"]
1520 .iter()
1521 .map(|v| {
1522 ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
1523 PRECISION,
1524 rug::Complex::parse(v).unwrap(),
1525 ))
1526 .expect("valid test value")
1527 })
1528 .collect::<Vec<_>>();
1529
1530 let zero = ComplexRugStrictFinite::<PRECISION>::zero();
1531 let sum = values.iter().fold(zero.clone(), |acc, x| acc + x);
1532 assert_eq!(sum, zero);
1533
1534 let neumaier =
1535 NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new_sequential(values);
1536 let sum = neumaier.result();
1537 assert_eq!(
1538 sum,
1539 ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
1540 PRECISION,
1541 rug::Complex::parse("(1e-100,2e-100)").unwrap(),
1542 ))
1543 .expect("valid test value")
1544 );
1545 println!("compensated sum = {}", sum);
1546 }
1547
1548 #[test]
1549 fn combine_matches_new_sequential_for_chunks() {
1550 let values = [
1551 "(1.0,2.0)",
1552 "(1e100,2e100)",
1553 "(1.0,2.0)",
1554 "(-1e100,-2e100)",
1555 "(1e-100,2e-100)",
1556 "(2.0,3.0)",
1557 ]
1558 .iter()
1559 .map(|v| {
1560 ComplexRugStrictFinite::<PRECISION>::try_new(rug::Complex::with_val(
1561 PRECISION,
1562 rug::Complex::parse(v).unwrap(),
1563 ))
1564 .expect("valid test value")
1565 })
1566 .collect::<Vec<_>>();
1567
1568 let mut chunk_1 = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
1569 chunk_1.push(values[0].clone());
1570 chunk_1.push(values[1].clone());
1571
1572 let mut chunk_2 = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
1573 chunk_2.push(values[2].clone());
1574 chunk_2.push(values[3].clone());
1575
1576 let mut chunk_3 = NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new();
1577 chunk_3.push(values[4].clone());
1578 chunk_3.push(values[5].clone());
1579
1580 chunk_1.combine(chunk_2);
1581 chunk_1.combine(chunk_3);
1582
1583 let sequential =
1584 NeumaierSum::<ComplexRugStrictFinite<PRECISION>>::new_sequential(values);
1585 assert_eq!(chunk_1.result(), sequential.result());
1586 }
1587 }
1588 }
1589 }
1590
1591 mod scaled_sum_sq_accumulator_tests {
1592 use super::*;
1593 use crate::ScalarCore;
1594
1595 const EPS: f64 = f64::EPSILON;
1596
1597 #[test]
1599 fn single_value() {
1600 let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1601 acc.push(NonNegativeRealScalar::new(3.0));
1602 let result = acc.result_sqrt();
1603 assert!((*result.as_raw_scalar_ref() - 3.0).abs() < EPS);
1604 }
1605
1606 #[test]
1608 fn three_four_five_sqrt() {
1609 let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1610 acc.push(NonNegativeRealScalar::new(3.0));
1611 acc.push(NonNegativeRealScalar::new(4.0));
1612 let result = acc.result_sqrt();
1613 assert!((*result.as_raw_scalar_ref() - 5.0).abs() < EPS);
1614 }
1615
1616 #[test]
1618 fn three_four_five_squared() {
1619 let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1620 acc.push(NonNegativeRealScalar::new(3.0));
1621 acc.push(NonNegativeRealScalar::new(4.0));
1622 let result = acc.result();
1623 assert!((*result.as_raw_scalar_ref() - 25.0).abs() < EPS);
1624 }
1625
1626 #[test]
1628 fn zero_vector_sqrt() {
1629 let acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1630 let result = acc.result_sqrt();
1631 assert_eq!(*result.as_raw_scalar_ref(), 0.0);
1632 }
1633
1634 #[test]
1636 fn zero_vector_squared() {
1637 let acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1638 let result = acc.result();
1639 assert_eq!(*result.as_raw_scalar_ref(), 0.0);
1640 }
1641
1642 #[test]
1644 fn negative_values() {
1645 let mut acc = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1646 acc.push(NonNegativeRealScalar::new(3.0));
1647 acc.push(NonNegativeRealScalar::new(4.0));
1648 let result = acc.result_sqrt();
1649 assert!((*result.as_raw_scalar_ref() - 5.0).abs() < EPS);
1650 }
1651
1652 #[test]
1654 fn combine_other_scale_larger() {
1655 let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1657 acc1.push(NonNegativeRealScalar::new(1.0));
1658 acc1.push(NonNegativeRealScalar::new(1.0));
1659
1660 let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1662 acc2.push(NonNegativeRealScalar::new(100.0));
1663 acc2.push(NonNegativeRealScalar::new(100.0));
1664
1665 acc1.combine(acc2);
1668 let result = acc1.result_sqrt();
1669 let expected = (1.0_f64 + 1.0 + 100.0 * 100.0 + 100.0 * 100.0).sqrt();
1670 assert!((*result.as_raw_scalar_ref() - expected).abs() < EPS * expected);
1671 }
1672
1673 #[test]
1675 fn combine_self_scale_larger() {
1676 let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1678 acc1.push(NonNegativeRealScalar::new(100.0));
1679 acc1.push(NonNegativeRealScalar::new(100.0));
1680
1681 let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1683 acc2.push(NonNegativeRealScalar::new(1.0));
1684 acc2.push(NonNegativeRealScalar::new(1.0));
1685
1686 acc1.combine(acc2);
1688 let result = acc1.result_sqrt();
1689 let expected = (100.0_f64 * 100.0 + 100.0 * 100.0 + 1.0 + 1.0).sqrt();
1690 assert!((*result.as_raw_scalar_ref() - expected).abs() < EPS * expected);
1691 }
1692
1693 #[test]
1695 fn combine_both_zero() {
1696 let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1697 let acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1698 acc1.combine(acc2);
1699 let result = acc1.result_sqrt();
1700 assert_eq!(*result.as_raw_scalar_ref(), 0.0);
1701 }
1702
1703 #[test]
1705 fn combine_one_zero() {
1706 let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1708
1709 let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1711 acc2.push(NonNegativeRealScalar::new(3.0));
1712 acc2.push(NonNegativeRealScalar::new(4.0));
1713
1714 acc1.combine(acc2);
1715 let result = acc1.result_sqrt();
1716 assert!((*result.as_raw_scalar_ref() - 5.0).abs() < EPS);
1717 }
1718
1719 #[test]
1721 fn combine_neumaier() {
1722 let mut acc1 = ScaledSumSqAccumulator::<NeumaierSum<f64>>::new();
1724 for _ in 0..100 {
1725 acc1.push(NonNegativeRealScalar::new(0.01));
1726 }
1727
1728 let mut acc2 = ScaledSumSqAccumulator::<NeumaierSum<f64>>::new();
1730 acc2.push(NonNegativeRealScalar::new(10.0));
1731
1732 acc1.combine(acc2);
1733 let result = acc1.result_sqrt();
1734
1735 let expected = (0.01_f64 + 100.0).sqrt();
1737 assert!((*result.as_raw_scalar_ref() - expected).abs() < EPS * expected);
1738 }
1739
1740 #[test]
1744 fn result_vs_result_sqrt() {
1745 let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1746 acc1.push(NonNegativeRealScalar::new(3.0));
1747 acc1.push(NonNegativeRealScalar::new(4.0));
1748
1749 let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1750 acc2.push(NonNegativeRealScalar::new(3.0));
1751 acc2.push(NonNegativeRealScalar::new(4.0));
1752
1753 let result = acc1.result();
1754 let sqrt_result = acc2.result_sqrt();
1755
1756 let sqrt_squared = (*sqrt_result.as_raw_scalar_ref()).powi(2);
1758
1759 assert!((*result.as_raw_scalar_ref() - sqrt_squared).abs() < EPS);
1760 }
1761
1762 #[test]
1764 fn extreme_scale_difference() {
1765 let mut acc1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1766 acc1.push(NonNegativeRealScalar::new(1e-8)); let mut acc2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1769 acc2.push(NonNegativeRealScalar::new(1e8)); acc1.combine(acc2);
1772 let result = acc1.result_sqrt();
1773
1774 let expected = ((1e-8_f64).powi(2) + (1e8_f64).powi(2)).sqrt();
1776 let rel_err = (*result.as_raw_scalar_ref() - expected).abs() / expected;
1777 assert!(rel_err < 1e-10);
1778 }
1779
1780 #[test]
1782 fn sequential_vs_combined() {
1783 let data = [1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0];
1784
1785 let mut acc_seq = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1787 for &val in &data {
1788 acc_seq.push(NonNegativeRealScalar::new(val));
1789 }
1790 let result_seq = acc_seq.result_sqrt();
1791
1792 let mut acc_chunk1 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1794 for &val in &data[0..3] {
1795 acc_chunk1.push(NonNegativeRealScalar::new(val));
1796 }
1797
1798 let mut acc_chunk2 = ScaledSumSqAccumulator::<NaiveSum<f64>>::new();
1799 for &val in &data[3..] {
1800 acc_chunk2.push(NonNegativeRealScalar::new(val));
1801 }
1802
1803 acc_chunk1.combine(acc_chunk2);
1804 let result_combined = acc_chunk1.result_sqrt();
1805
1806 assert!(
1808 (*result_seq.as_raw_scalar_ref() - *result_combined.as_raw_scalar_ref()).abs()
1809 < EPS
1810 );
1811 }
1812 }
1813}