1use std::collections::HashMap;
12use std::marker::PhantomData;
13use std::sync::OnceLock;
14use std::time::Duration;
15
16use algebra::JoinSemilattice;
17use enum_as_inner::EnumAsInner;
18use serde::Deserialize;
19use serde::Serialize;
20use serde::de::DeserializeOwned;
21use typeuri::Named;
22
23use crate::config;
25
26pub trait Accumulator {
28 type State;
30 type Update;
33
34 fn accumulate(&self, state: &mut Self::State, update: Self::Update) -> anyhow::Result<()>;
36
37 fn reducer_spec(&self) -> Option<ReducerSpec>;
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, typeuri::Named)]
43pub struct ReducerSpec {
44 pub typehash: u64,
46 pub builder_params: Option<wirevalue::Any>,
48}
49wirevalue::register_type!(ReducerSpec);
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Named, Default)]
53pub struct StreamingReducerOpts {
54 pub max_update_interval: Option<Duration>,
57 pub initial_update_interval: Option<Duration>,
61}
62
63#[derive(
65 Debug,
66 Clone,
67 PartialEq,
68 Serialize,
69 Deserialize,
70 EnumAsInner,
71 typeuri::Named
72)]
73pub enum ReducerMode {
74 Streaming(StreamingReducerOpts),
76 Once(usize),
78}
79
80impl Default for ReducerMode {
81 fn default() -> Self {
82 ReducerMode::Streaming(StreamingReducerOpts::default())
83 }
84}
85
86impl ReducerMode {
87 pub(crate) fn max_update_interval(&self) -> Duration {
88 match self {
89 ReducerMode::Streaming(opts) => opts
90 .max_update_interval
91 .unwrap_or(hyperactor_config::global::get(config::SPLIT_MAX_BUFFER_AGE)),
92 ReducerMode::Once(_) => Duration::MAX,
93 }
94 }
95
96 pub(crate) fn initial_update_interval(&self) -> Duration {
97 match self {
98 ReducerMode::Streaming(opts) => opts
99 .initial_update_interval
100 .unwrap_or(Duration::from_millis(1)),
101 ReducerMode::Once(_) => Duration::MAX,
102 }
103 }
104}
105
106pub trait CommReducer {
112 type Update;
114
115 fn reduce(&self, left: Self::Update, right: Self::Update) -> anyhow::Result<Self::Update>;
117}
118
119pub trait ErasedCommReducer {
121 fn reduce_erased(
123 &self,
124 left: &wirevalue::Any,
125 right: &wirevalue::Any,
126 ) -> anyhow::Result<wirevalue::Any>;
127
128 fn reduce_updates(
131 &self,
132 updates: Vec<wirevalue::Any>,
133 ) -> Result<wirevalue::Any, (anyhow::Error, Vec<wirevalue::Any>)> {
134 if updates.is_empty() {
135 return Err((anyhow::anyhow!("empty updates"), updates));
136 }
137 if updates.len() == 1 {
138 return Ok(updates.into_iter().next().expect("checked above"));
139 }
140
141 let mut iter = updates.iter();
142 let first = iter.next().unwrap();
143 let second = iter.next().unwrap();
144 let init = match self.reduce_erased(first, second) {
145 Ok(v) => v,
146 Err(e) => return Err((e, updates)),
147 };
148 let reduced = match iter.try_fold(init, |acc, e| self.reduce_erased(&acc, e)) {
149 Ok(v) => v,
150 Err(e) => return Err((e, updates)),
151 };
152 Ok(reduced)
153 }
154
155 fn typehash(&self) -> u64;
157}
158
159impl<R, T> ErasedCommReducer for R
160where
161 R: CommReducer<Update = T> + Named,
162 T: Serialize + DeserializeOwned + Named,
163{
164 fn reduce_erased(
165 &self,
166 left: &wirevalue::Any,
167 right: &wirevalue::Any,
168 ) -> anyhow::Result<wirevalue::Any> {
169 let left = left.deserialized::<T>()?;
170 let right = right.deserialized::<T>()?;
171 let result = self.reduce(left, right)?;
172 Ok(wirevalue::Any::serialize(&result)?)
173 }
174
175 fn typehash(&self) -> u64 {
176 R::typehash()
177 }
178}
179
180pub struct ReducerFactory {
185 pub typehash_f: fn() -> u64,
188 pub builder_f: fn(
190 Option<wirevalue::Any>,
191 ) -> anyhow::Result<Box<dyn ErasedCommReducer + Sync + Send + 'static>>,
192}
193
194inventory::collect!(ReducerFactory);
195
196inventory::submit! {
197 ReducerFactory {
198 typehash_f: <SumReducer<i64> as Named>::typehash,
199 builder_f: |_| Ok(Box::new(SumReducer::<i64>(PhantomData))),
200 }
201}
202inventory::submit! {
203 ReducerFactory {
204 typehash_f: <SumReducer<u64> as Named>::typehash,
205 builder_f: |_| Ok(Box::new(SumReducer::<u64>(PhantomData))),
206 }
207}
208inventory::submit! {
209 ReducerFactory {
210 typehash_f: <SemilatticeReducer<Max<i64>> as Named>::typehash,
211 builder_f: |_| Ok(Box::new(SemilatticeReducer::<Max<i64>>(PhantomData))),
212 }
213}
214inventory::submit! {
215 ReducerFactory {
216 typehash_f: <SemilatticeReducer<Max<u64>> as Named>::typehash,
217 builder_f: |_| Ok(Box::new(SemilatticeReducer::<Max<u64>>(PhantomData))),
218 }
219}
220inventory::submit! {
221 ReducerFactory {
222 typehash_f: <SemilatticeReducer<Min<i64>> as Named>::typehash,
223 builder_f: |_| Ok(Box::new(SemilatticeReducer::<Min<i64>>(PhantomData))),
224 }
225}
226inventory::submit! {
227 ReducerFactory {
228 typehash_f: <SemilatticeReducer<Min<u64>> as Named>::typehash,
229 builder_f: |_| Ok(Box::new(SemilatticeReducer::<Min<u64>>(PhantomData))),
230 }
231}
232inventory::submit! {
233 ReducerFactory {
234 typehash_f: <SemilatticeReducer<WatermarkUpdate<i64>> as Named>::typehash,
235 builder_f: |_| Ok(Box::new(SemilatticeReducer::<WatermarkUpdate<i64>>(PhantomData))),
236 }
237}
238inventory::submit! {
239 ReducerFactory {
240 typehash_f: <SemilatticeReducer<WatermarkUpdate<u64>> as Named>::typehash,
241 builder_f: |_| Ok(Box::new(SemilatticeReducer::<WatermarkUpdate<u64>>(PhantomData))),
242 }
243}
244inventory::submit! {
245 ReducerFactory {
246 typehash_f: <SemilatticeReducer<GCounterUpdate> as Named>::typehash,
247 builder_f: |_| Ok(Box::new(SemilatticeReducer::<GCounterUpdate>(PhantomData))),
248 }
249}
250inventory::submit! {
251 ReducerFactory {
252 typehash_f: <SemilatticeReducer<PNCounterUpdate> as Named>::typehash,
253 builder_f: |_| Ok(Box::new(SemilatticeReducer::<PNCounterUpdate>(PhantomData))),
254 }
255}
256inventory::submit! {
257 ReducerFactory {
258 typehash_f: <UnitReducer as Named>::typehash,
259 builder_f: |_| Ok(Box::new(UnitReducer)),
260 }
261}
262
263pub(crate) fn resolve_reducer(
266 typehash: u64,
267 builder_params: Option<wirevalue::Any>,
268) -> anyhow::Result<Option<Box<dyn ErasedCommReducer + Sync + Send + 'static>>> {
269 static FACTORY_MAP: OnceLock<HashMap<u64, &'static ReducerFactory>> = OnceLock::new();
270 let factories = FACTORY_MAP.get_or_init(|| {
271 let mut map = HashMap::new();
272 for factory in inventory::iter::<ReducerFactory> {
273 map.insert((factory.typehash_f)(), factory);
274 }
275 map
276 });
277
278 factories
279 .get(&typehash)
280 .map(|f| (f.builder_f)(builder_params))
281 .transpose()
282}
283
284#[derive(typeuri::Named)]
285struct SumReducer<T>(PhantomData<T>);
286
287impl<T: std::ops::Add<Output = T> + Copy + 'static> CommReducer for SumReducer<T> {
288 type Update = T;
289
290 fn reduce(&self, left: T, right: T) -> anyhow::Result<T> {
291 Ok(left + right)
292 }
293}
294
295struct SumAccumulator<T>(PhantomData<T>);
298
299impl<T: std::ops::Add<Output = T> + Copy + Named + 'static> Accumulator for SumAccumulator<T> {
300 type State = T;
301 type Update = T;
302
303 fn accumulate(&self, state: &mut T, update: T) -> anyhow::Result<()> {
304 *state = *state + update;
305 Ok(())
306 }
307
308 fn reducer_spec(&self) -> Option<ReducerSpec> {
309 Some(ReducerSpec {
310 typehash: <SumReducer<T> as Named>::typehash(),
311 builder_params: None,
312 })
313 }
314}
315
316pub fn sum<T: std::ops::Add<Output = T> + Copy + Named + 'static>()
341-> impl Accumulator<State = T, Update = T> {
342 SumAccumulator(PhantomData)
343}
344
345#[derive(typeuri::Named)]
347struct UnitReducer;
348
349impl CommReducer for UnitReducer {
350 type Update = ();
351
352 fn reduce(&self, _left: (), _right: ()) -> anyhow::Result<()> {
353 Ok(())
354 }
355}
356
357struct UnitAccumulator;
359
360impl Accumulator for UnitAccumulator {
361 type State = ();
362 type Update = ();
363
364 fn accumulate(&self, _state: &mut (), _update: ()) -> anyhow::Result<()> {
365 Ok(())
366 }
367
368 fn reducer_spec(&self) -> Option<ReducerSpec> {
369 Some(ReducerSpec {
370 typehash: <UnitReducer as Named>::typehash(),
371 builder_params: None,
372 })
373 }
374}
375
376pub fn unit() -> impl Accumulator<State = (), Update = ()> {
383 UnitAccumulator
384}
385
386#[derive(typeuri::Named)]
388struct SemilatticeReducer<L>(PhantomData<L>);
389
390impl<L: JoinSemilattice + Clone> CommReducer for SemilatticeReducer<L> {
391 type Update = L;
392
393 fn reduce(&self, left: L, right: L) -> anyhow::Result<L> {
394 Ok(left.join(&right))
395 }
396}
397
398struct SemilatticeAccumulator<L>(PhantomData<L>);
400
401impl<L: JoinSemilattice + Clone + Named + 'static> Accumulator for SemilatticeAccumulator<L> {
402 type State = L;
403 type Update = L;
404
405 fn accumulate(&self, state: &mut L, update: L) -> anyhow::Result<()> {
406 *state = state.join(&update);
407 Ok(())
408 }
409
410 fn reducer_spec(&self) -> Option<ReducerSpec> {
411 Some(ReducerSpec {
412 typehash: <SemilatticeReducer<L> as Named>::typehash(),
413 builder_params: None,
414 })
415 }
416}
417
418pub fn join_semilattice<L: JoinSemilattice + Clone + Named + 'static>()
432-> impl Accumulator<State = L, Update = L> {
433 SemilatticeAccumulator::<L>(PhantomData)
434}
435
436pub use algebra::Max;
438pub use algebra::Min;
440
441#[derive(Default, Debug, Clone, Serialize, Deserialize, typeuri::Named)]
463pub struct WatermarkUpdate<T>(algebra::LatticeMap<usize, algebra::LWW<T>>);
464
465impl<T: Ord + Clone> WatermarkUpdate<T> {
466 pub fn get(&self) -> &T {
471 self.0
472 .iter()
473 .map(|(_, lww)| &lww.value)
474 .min()
475 .expect("watermark should have been initialized")
476 }
477
478 pub fn get_rank(&self, rank: usize) -> Option<&T> {
480 self.0.get(&rank).map(|lww| &lww.value)
481 }
482
483 pub fn num_ranks(&self) -> usize {
485 self.0.len()
486 }
487}
488
489impl<T> From<(usize, T, u64)> for WatermarkUpdate<T> {
490 fn from((rank, value, timestamp): (usize, T, u64)) -> Self {
496 let mut map = algebra::LatticeMap::new();
497 map.insert(rank, algebra::LWW::new(value, timestamp, rank as u64));
499 Self(map)
500 }
501}
502
503impl<T: Clone + PartialEq> JoinSemilattice for WatermarkUpdate<T> {
504 fn join(&self, other: &Self) -> Self {
505 WatermarkUpdate(self.0.join(&other.0))
506 }
507}
508
509#[derive(Default, Debug, Clone, Serialize, Deserialize, typeuri::Named)]
521pub struct GCounterUpdate(algebra::LatticeMap<usize, Max<u64>>);
522wirevalue::register_type!(GCounterUpdate);
523
524impl GCounterUpdate {
525 pub fn get(&self) -> u64 {
527 self.0.iter().map(|(_, max)| max.0).sum()
528 }
529
530 pub fn get_rank(&self, rank: usize) -> Option<u64> {
532 self.0.get(&rank).map(|max| max.0)
533 }
534
535 pub fn num_ranks(&self) -> usize {
537 self.0.len()
538 }
539}
540
541impl From<(usize, u64)> for GCounterUpdate {
542 fn from((rank, count): (usize, u64)) -> Self {
544 let mut map = algebra::LatticeMap::new();
545 map.insert(rank, Max(count));
546 Self(map)
547 }
548}
549
550impl JoinSemilattice for GCounterUpdate {
551 fn join(&self, other: &Self) -> Self {
552 GCounterUpdate(self.0.join(&other.0))
553 }
554}
555
556#[derive(Default, Debug, Clone, Serialize, Deserialize, typeuri::Named)]
563pub struct PNCounterUpdate {
564 p: algebra::LatticeMap<usize, Max<u64>>,
565 n: algebra::LatticeMap<usize, Max<u64>>,
566}
567wirevalue::register_type!(PNCounterUpdate);
568
569impl PNCounterUpdate {
570 pub fn get(&self) -> i64 {
572 let p: u64 = self.p.iter().map(|(_, m)| m.0).sum();
573 let n: u64 = self.n.iter().map(|(_, m)| m.0).sum();
574 p as i64 - n as i64
575 }
576
577 pub fn inc(rank: usize, delta: u64) -> Self {
579 let mut p = algebra::LatticeMap::new();
580 p.insert(rank, Max(delta));
581 Self {
582 p,
583 n: algebra::LatticeMap::new(),
584 }
585 }
586
587 pub fn dec(rank: usize, delta: u64) -> Self {
589 let mut n = algebra::LatticeMap::new();
590 n.insert(rank, Max(delta));
591 Self {
592 p: algebra::LatticeMap::new(),
593 n,
594 }
595 }
596
597 pub fn num_inc_ranks(&self) -> usize {
599 self.p.len()
600 }
601
602 pub fn num_dec_ranks(&self) -> usize {
604 self.n.len()
605 }
606}
607
608impl JoinSemilattice for PNCounterUpdate {
609 fn join(&self, other: &Self) -> Self {
610 PNCounterUpdate {
611 p: self.p.join(&other.p),
612 n: self.n.join(&other.n),
613 }
614 }
615}
616
617#[cfg(test)]
618mod tests {
619 use std::fmt::Debug;
620
621 use maplit::hashmap;
622 use typeuri::Named;
623
624 use super::*;
625
626 fn serialize<T: Serialize + Named>(values: Vec<T>) -> Vec<wirevalue::Any> {
627 values
628 .into_iter()
629 .map(|n| wirevalue::Any::serialize(&n).unwrap())
630 .collect()
631 }
632
633 #[test]
634 fn test_comm_reducer_numeric() {
635 let u64_numbers_sum: Vec<_> = serialize(vec![1u64, 3u64, 1100u64]);
636 let i64_numbers_sum: Vec<_> = serialize(vec![-123i64, 33i64, 110i64]);
637 let u64_numbers_max: Vec<_> = serialize(vec![Max(1u64), Max(3u64), Max(1100u64)]);
638 let i64_numbers_max: Vec<_> = serialize(vec![Max(-123i64), Max(33i64), Max(110i64)]);
639 let u64_numbers_min: Vec<_> = serialize(vec![Min(1u64), Min(3u64), Min(1100u64)]);
640 let i64_numbers_min: Vec<_> = serialize(vec![Min(-123i64), Min(33i64), Min(110i64)]);
641 {
642 let typehash = <SemilatticeReducer<Max<u64>> as Named>::typehash();
643 assert_eq!(
644 resolve_reducer(typehash, None)
645 .unwrap()
646 .unwrap()
647 .reduce_updates(u64_numbers_max.clone())
648 .unwrap()
649 .deserialized::<Max<u64>>()
650 .unwrap(),
651 Max(1100u64),
652 );
653
654 let typehash = <SemilatticeReducer<Min<u64>> as Named>::typehash();
655 assert_eq!(
656 resolve_reducer(typehash, None)
657 .unwrap()
658 .unwrap()
659 .reduce_updates(u64_numbers_min.clone())
660 .unwrap()
661 .deserialized::<Min<u64>>()
662 .unwrap(),
663 Min(1u64),
664 );
665
666 let typehash = <SumReducer<u64> as Named>::typehash();
667 assert_eq!(
668 resolve_reducer(typehash, None)
669 .unwrap()
670 .unwrap()
671 .reduce_updates(u64_numbers_sum)
672 .unwrap()
673 .deserialized::<u64>()
674 .unwrap(),
675 1104u64,
676 );
677 }
678
679 {
680 let typehash = <SemilatticeReducer<Max<i64>> as Named>::typehash();
681 assert_eq!(
682 resolve_reducer(typehash, None)
683 .unwrap()
684 .unwrap()
685 .reduce_updates(i64_numbers_max.clone())
686 .unwrap()
687 .deserialized::<Max<i64>>()
688 .unwrap(),
689 Max(110i64),
690 );
691
692 let typehash = <SemilatticeReducer<Min<i64>> as Named>::typehash();
693 assert_eq!(
694 resolve_reducer(typehash, None)
695 .unwrap()
696 .unwrap()
697 .reduce_updates(i64_numbers_min.clone())
698 .unwrap()
699 .deserialized::<Min<i64>>()
700 .unwrap(),
701 Min(-123i64),
702 );
703
704 let typehash = <SumReducer<i64> as Named>::typehash();
705 assert_eq!(
706 resolve_reducer(typehash, None)
707 .unwrap()
708 .unwrap()
709 .reduce_updates(i64_numbers_sum)
710 .unwrap()
711 .deserialized::<i64>()
712 .unwrap(),
713 20i64,
714 );
715 }
716 }
717
718 #[test]
719 fn test_comm_reducer_watermark() {
720 let u64_updates = serialize::<WatermarkUpdate<u64>>(
722 vec![
723 (1, 1, 0), (0, 2, 1), (0, 1, 2), (3, 35, 3), (0, 9, 4), (1, 10, 5), (3, 32, 6), (3, 0, 7), (3, 321, 8), ]
733 .into_iter()
734 .map(|(k, v, ts)| WatermarkUpdate::from((k, v, ts)))
735 .collect(),
736 );
737 let i64_updates: Vec<_> = serialize::<WatermarkUpdate<i64>>(
738 vec![
739 (0, 2, 0), (1, 1, 1), (3, 35, 2), (0, 1, 3), (1, -10, 4), (3, 32, 5), (3, 0, 6), (3, -99, 7), (0, -9, 8), ]
749 .into_iter()
750 .map(WatermarkUpdate::from)
751 .collect(),
752 );
753
754 fn verify<T: Ord + Clone + PartialEq + DeserializeOwned + Debug + Named>(
755 updates: Vec<wirevalue::Any>,
756 expected: HashMap<usize, T>,
757 ) {
758 let typehash = <SemilatticeReducer<WatermarkUpdate<T>> as Named>::typehash();
759 let result = resolve_reducer(typehash, None)
760 .unwrap()
761 .unwrap()
762 .reduce_updates(updates)
763 .unwrap()
764 .deserialized::<WatermarkUpdate<T>>()
765 .unwrap();
766
767 for (rank, expected_value) in &expected {
769 assert_eq!(
770 result.get_rank(*rank).unwrap(),
771 expected_value,
772 "Mismatch for rank {rank}"
773 );
774 }
775 assert_eq!(result.num_ranks(), expected.len());
777 }
778
779 verify::<i64>(
780 i64_updates,
781 hashmap! {
782 0 => -9, 1 => -10, 3 => -99, },
786 );
787
788 verify::<u64>(
789 u64_updates,
790 hashmap! {
791 0 => 9, 1 => 10, 3 => 321, },
795 );
796 }
797
798 #[test]
799 fn test_accum_reducer_numeric() {
800 assert_eq!(
801 sum::<u64>().reducer_spec().unwrap().typehash,
802 <SumReducer::<u64> as Named>::typehash(),
803 );
804 assert_eq!(
805 sum::<i64>().reducer_spec().unwrap().typehash,
806 <SumReducer::<i64> as Named>::typehash(),
807 );
808
809 assert_eq!(
810 join_semilattice::<Min<u64>>()
811 .reducer_spec()
812 .unwrap()
813 .typehash,
814 <SemilatticeReducer<Min<u64>> as Named>::typehash(),
815 );
816 assert_eq!(
817 join_semilattice::<Min<i64>>()
818 .reducer_spec()
819 .unwrap()
820 .typehash,
821 <SemilatticeReducer<Min<i64>> as Named>::typehash(),
822 );
823
824 assert_eq!(
825 join_semilattice::<Max<u64>>()
826 .reducer_spec()
827 .unwrap()
828 .typehash,
829 <SemilatticeReducer<Max<u64>> as Named>::typehash(),
830 );
831 assert_eq!(
832 join_semilattice::<Max<i64>>()
833 .reducer_spec()
834 .unwrap()
835 .typehash,
836 <SemilatticeReducer<Max<i64>> as Named>::typehash(),
837 );
838 assert_eq!(
839 unit().reducer_spec().unwrap().typehash,
840 <UnitReducer as Named>::typehash(),
841 );
842 }
843
844 #[test]
845 fn test_comm_reducer_unit() {
846 let unit_updates = serialize(vec![(), (), ()]);
847 let typehash = <UnitReducer as Named>::typehash();
848 assert_eq!(
849 resolve_reducer(typehash, None)
850 .unwrap()
851 .unwrap()
852 .reduce_updates(unit_updates)
853 .unwrap()
854 .deserialized::<()>()
855 .unwrap(),
856 (),
857 );
858 }
859
860 #[test]
861 fn test_accum_reducer_watermark() {
862 fn verify<T: Clone + PartialEq + Named + 'static>() {
863 assert_eq!(
864 join_semilattice::<WatermarkUpdate<T>>()
865 .reducer_spec()
866 .unwrap()
867 .typehash,
868 <SemilatticeReducer<WatermarkUpdate<T>> as Named>::typehash(),
869 );
870 }
871 verify::<u64>();
872 verify::<i64>();
873 }
874
875 #[test]
876 fn test_watermark_accumulator() {
877 let accumulator = join_semilattice::<WatermarkUpdate<u64>>();
878 let ranks_values_expectations = [
879 (0, 1003, 0, 1003),
881 (1, 1002, 1, 1002),
882 (2, 1001, 2, 1001),
883 (0, 100, 3, 100),
885 (1, 101, 4, 100),
886 (2, 102, 5, 100),
887 (0, 100, 6, 100),
889 (1, 101, 7, 100),
890 (2, 102, 8, 100),
891 (0, 1000, 9, 101),
893 (1, 1100, 10, 102),
895 (2, 1200, 11, 1000),
897 (0, 1001, 12, 1001),
899 (1, 1101, 13, 1001),
900 (2, 1201, 14, 1001),
901 (2, 102, 15, 102),
903 (1, 101, 16, 101),
904 (0, 100, 17, 100),
905 ];
906 let mut state = WatermarkUpdate::default();
907 for (rank, value, ts, expected) in ranks_values_expectations {
908 accumulator
909 .accumulate(&mut state, WatermarkUpdate::from((rank, value, ts)))
910 .unwrap();
911 assert_eq!(
912 state.get(),
913 &expected,
914 "rank is {rank}; value is {value}; ts is {ts}"
915 );
916 }
917 }
918
919 #[test]
920 fn test_comm_reducer_gcounter() {
921 let updates = serialize::<GCounterUpdate>(vec![
923 GCounterUpdate::from((0, 10)),
924 GCounterUpdate::from((1, 20)),
925 GCounterUpdate::from((0, 15)), GCounterUpdate::from((2, 5)),
927 GCounterUpdate::from((1, 25)), ]);
929
930 let typehash = <SemilatticeReducer<GCounterUpdate> as Named>::typehash();
931 let result = resolve_reducer(typehash, None)
932 .unwrap()
933 .unwrap()
934 .reduce_updates(updates)
935 .unwrap()
936 .deserialized::<GCounterUpdate>()
937 .unwrap();
938
939 assert_eq!(result.get_rank(0), Some(15));
941 assert_eq!(result.get_rank(1), Some(25));
942 assert_eq!(result.get_rank(2), Some(5));
943 assert_eq!(result.num_ranks(), 3);
944 assert_eq!(result.get(), 45);
946 }
947
948 #[test]
949 fn test_accum_reducer_gcounter() {
950 assert_eq!(
951 join_semilattice::<GCounterUpdate>()
952 .reducer_spec()
953 .unwrap()
954 .typehash,
955 <SemilatticeReducer<GCounterUpdate> as Named>::typehash(),
956 );
957 }
958
959 #[test]
960 fn test_gcounter_accumulator() {
961 let accumulator = join_semilattice::<GCounterUpdate>();
962 let ranks_counts_expectations: [(usize, u64, u64); 17] = [
964 (0, 1000, 1000),
966 (1, 100, 1100),
967 (2, 10, 1110),
968 (2, 20, 1120),
970 (1, 200, 1220),
971 (0, 2000, 2220),
972 (0, 2000, 2220),
974 (1, 200, 2220),
975 (2, 20, 2220),
976 (0, 1, 2220),
978 (1, 1, 2220),
979 (2, 1, 2220),
980 (2, 5000, 7200), (1, 6000, 13000), (0, 10000, 21000), (0, 10001, 21001),
988 (1, 6001, 21002),
989 ];
990 let mut state = GCounterUpdate::default();
991 for (rank, count, expected) in ranks_counts_expectations {
992 accumulator
993 .accumulate(&mut state, GCounterUpdate::from((rank, count)))
994 .unwrap();
995 assert_eq!(state.get(), expected, "rank is {rank}; count is {count}");
996 }
997 assert_eq!(state.get_rank(0), Some(10001));
999 assert_eq!(state.get_rank(1), Some(6001));
1000 assert_eq!(state.get_rank(2), Some(5000));
1001 assert_eq!(state.get_rank(3), None);
1002 assert_eq!(state.num_ranks(), 3);
1003 }
1004
1005 #[test]
1006 fn test_gcounter_commutativity() {
1007 let updates = [
1009 GCounterUpdate::from((0, 10)),
1010 GCounterUpdate::from((1, 20)),
1011 GCounterUpdate::from((0, 15)),
1012 GCounterUpdate::from((2, 5)),
1013 GCounterUpdate::from((1, 25)),
1014 ];
1015
1016 let accumulator = join_semilattice::<GCounterUpdate>();
1018 let mut forward = GCounterUpdate::default();
1019 for update in updates.iter().cloned() {
1020 accumulator.accumulate(&mut forward, update).unwrap();
1021 }
1022
1023 let mut reverse = GCounterUpdate::default();
1025 for update in updates.iter().rev().cloned() {
1026 accumulator.accumulate(&mut reverse, update).unwrap();
1027 }
1028
1029 assert_eq!(forward.get(), reverse.get());
1030 assert_eq!(forward.get(), 45); assert_eq!(forward.get_rank(0), reverse.get_rank(0));
1032 assert_eq!(forward.get_rank(1), reverse.get_rank(1));
1033 assert_eq!(forward.get_rank(2), reverse.get_rank(2));
1034 }
1035
1036 #[test]
1037 fn test_comm_reducer_pncounter() {
1038 let updates = serialize::<PNCounterUpdate>(vec![
1040 PNCounterUpdate::inc(0, 10),
1041 PNCounterUpdate::inc(1, 20),
1042 PNCounterUpdate::dec(0, 5),
1043 PNCounterUpdate::inc(0, 15), PNCounterUpdate::dec(1, 8),
1045 PNCounterUpdate::dec(0, 7), ]);
1047
1048 let typehash = <SemilatticeReducer<PNCounterUpdate> as Named>::typehash();
1049 let result = resolve_reducer(typehash, None)
1050 .unwrap()
1051 .unwrap()
1052 .reduce_updates(updates)
1053 .unwrap()
1054 .deserialized::<PNCounterUpdate>()
1055 .unwrap();
1056
1057 assert_eq!(result.get(), 20);
1062 assert_eq!(result.num_inc_ranks(), 2);
1063 assert_eq!(result.num_dec_ranks(), 2);
1064 }
1065
1066 #[test]
1067 fn test_accum_reducer_pncounter() {
1068 assert_eq!(
1069 join_semilattice::<PNCounterUpdate>()
1070 .reducer_spec()
1071 .unwrap()
1072 .typehash,
1073 <SemilatticeReducer<PNCounterUpdate> as Named>::typehash(),
1074 );
1075 }
1076
1077 #[test]
1078 fn test_pncounter_accumulator() {
1079 let accumulator = join_semilattice::<PNCounterUpdate>();
1080 #[derive(Clone, Copy, Debug)]
1082 enum Op {
1083 Inc(usize, u64),
1084 Dec(usize, u64),
1085 }
1086 use Op::*;
1087
1088 let ops_expectations = [
1092 (Inc(0, 100), 100), (Inc(1, 50), 150), (Inc(2, 25), 175), (Dec(0, 10), 165), (Dec(1, 5), 160), (Dec(2, 2), 158), (Inc(0, 200), 258), (Inc(1, 100), 308), (Inc(2, 50), 333), (Dec(0, 20), 323), (Dec(1, 15), 313), (Dec(2, 5), 310), (Inc(0, 200), 310),
1110 (Dec(1, 15), 310),
1111 (Inc(0, 1), 310),
1113 (Dec(0, 1), 310),
1114 (Dec(2, 60), 255), (Dec(1, 120), 150), (Inc(2, 60), 160), (Inc(0, 1000), 960), (Dec(2, 100), 920), ];
1123
1124 let mut state = PNCounterUpdate::default();
1125 for (i, (op, expected)) in ops_expectations.iter().enumerate() {
1126 let update = match op {
1127 Inc(rank, delta) => PNCounterUpdate::inc(*rank, *delta),
1128 Dec(rank, delta) => PNCounterUpdate::dec(*rank, *delta),
1129 };
1130 accumulator.accumulate(&mut state, update).unwrap();
1131 assert_eq!(state.get(), *expected, "step {i}: {op:?}");
1132 }
1133
1134 assert_eq!(state.num_inc_ranks(), 3);
1136 assert_eq!(state.num_dec_ranks(), 3);
1137 }
1138
1139 #[test]
1140 fn test_pncounter_commutativity() {
1141 let updates = [
1143 PNCounterUpdate::inc(0, 10),
1144 PNCounterUpdate::inc(1, 20),
1145 PNCounterUpdate::dec(0, 5),
1146 PNCounterUpdate::inc(0, 15),
1147 PNCounterUpdate::dec(1, 8),
1148 PNCounterUpdate::dec(2, 3),
1149 PNCounterUpdate::inc(2, 12),
1150 ];
1151
1152 let accumulator = join_semilattice::<PNCounterUpdate>();
1154 let mut forward = PNCounterUpdate::default();
1155 for update in updates.iter().cloned() {
1156 accumulator.accumulate(&mut forward, update).unwrap();
1157 }
1158
1159 let mut reverse = PNCounterUpdate::default();
1161 for update in updates.iter().rev().cloned() {
1162 accumulator.accumulate(&mut reverse, update).unwrap();
1163 }
1164
1165 assert_eq!(forward.get(), reverse.get());
1166 assert_eq!(forward.get(), 31); assert_eq!(forward.num_inc_ranks(), reverse.num_inc_ranks());
1168 assert_eq!(forward.num_dec_ranks(), reverse.num_dec_ranks());
1169 }
1170}