1use std::cmp::Ordering;
15use std::collections::HashMap;
16use std::collections::hash_map::Entry;
17use std::hash::Hash;
18use std::marker::PhantomData;
19use std::mem;
20use std::mem::MaybeUninit;
21use std::ops::Range;
22use std::ptr;
23use std::ptr::NonNull;
24
25use futures::Future;
26use ndslice::extent;
27use ndslice::view;
28use ndslice::view::Ranked;
29use ndslice::view::Region;
30use serde::Deserialize;
31use serde::Serialize;
32use typeuri::Named;
33
34use crate::accum::Accumulator;
35use crate::accum::CommReducer;
36use crate::accum::ReducerSpec;
37
38pub mod rle;
40mod value_overlay;
41pub use value_overlay::BuildError;
42pub use value_overlay::ValueOverlay;
43
44#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
46pub enum ValueMeshError {
47 #[error("invalid value mesh: expected {expected} ranks, but contains {actual} ranks")]
49 InvalidRankCardinality {
50 expected: usize,
52 actual: usize,
54 },
55}
56
57impl From<view::InvalidCardinality> for ValueMeshError {
58 fn from(value: view::InvalidCardinality) -> Self {
59 Self::InvalidRankCardinality {
60 expected: value.expected,
61 actual: value.actual,
62 }
63 }
64}
65
66#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ValueMesh<T> {
75 region: Region,
80
81 rep: Rep<T>,
92}
93
94#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
105struct Run {
106 start: u64,
108 end: u64,
110 id: u32,
112}
113
114impl Run {
115 fn new(start: usize, end: usize, id: u32) -> Self {
120 Self {
121 start: start as u64,
122 end: end as u64,
123 id,
124 }
125 }
126}
127
128impl TryFrom<Run> for (Range<usize>, u32) {
129 type Error = &'static str;
130
131 #[allow(clippy::result_large_err)]
140 fn try_from(r: Run) -> Result<Self, Self::Error> {
141 let start = usize::try_from(r.start).map_err(|_| "run.start too large")?;
142 let end = usize::try_from(r.end).map_err(|_| "run.end too large")?;
143 Ok((start..end, r.id))
144 }
145}
146
147#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] enum Rep<T> {
162 Dense {
169 values: Vec<T>,
172 },
173
174 Compressed {
186 table: Vec<T>,
188
189 runs: Vec<Run>,
192 },
193}
194
195impl<T> Default for ValueMesh<T> {
200 fn default() -> Self {
201 Self::empty()
202 }
203}
204
205impl<T> ValueMesh<T> {
206 pub fn empty() -> Self {
213 let region = extent!(r = 0).into();
215 Self::new_unchecked(region, Vec::<T>::new())
216 }
217
218 #[allow(clippy::result_large_err)]
231 pub fn new(region: Region, ranks: Vec<T>) -> std::result::Result<Self, ValueMeshError> {
232 let (actual, expected) = (ranks.len(), region.num_ranks());
233 if actual != expected {
234 return Err(ValueMeshError::InvalidRankCardinality { expected, actual });
235 }
236 Ok(Self {
237 region,
238 rep: Rep::Dense { values: ranks },
239 })
240 }
241
242 #[inline]
245 pub(crate) fn new_unchecked(region: Region, ranks: Vec<T>) -> Self {
246 debug_assert_eq!(region.num_ranks(), ranks.len());
247 Self {
248 region,
249 rep: Rep::Dense { values: ranks },
250 }
251 }
252}
253
254impl<T: Clone> ValueMesh<T> {
255 pub fn from_single(region: Region, s: T) -> Self {
263 let n = region.num_ranks();
264 if n == 0 {
265 return Self {
266 region,
267 rep: Rep::Compressed {
268 table: Vec::new(),
269 runs: Vec::new(),
270 },
271 };
272 }
273
274 let table = vec![s];
275 let runs = vec![Run::new(0, n, 0)];
276 Self {
277 region,
278 rep: Rep::Compressed { table, runs },
279 }
280 }
281}
282
283impl<T: Clone + Default> ValueMesh<T> {
284 pub fn from_default(region: Region) -> Self {
290 ValueMesh::<T>::from_single(region, T::default())
291 }
292}
293
294impl<T: Eq + Hash> ValueMesh<T> {
295 #[allow(clippy::result_large_err)]
304 pub fn from_ranges_with_default(
305 region: Region,
306 default: T,
307 mut ranges: Vec<(Range<usize>, T)>,
308 ) -> std::result::Result<Self, ValueMeshError> {
309 let n = region.num_ranks();
310
311 if n == 0 {
312 return Ok(Self {
313 region,
314 rep: Rep::Compressed {
315 table: Vec::new(),
316 runs: Vec::new(),
317 },
318 });
319 }
320
321 for (r, _) in &ranges {
323 if r.is_empty() {
324 return Err(ValueMeshError::InvalidRankCardinality {
325 expected: n,
326 actual: 0,
327 }); }
329 if r.end > n {
330 return Err(ValueMeshError::InvalidRankCardinality {
331 expected: n,
332 actual: r.end,
333 });
334 }
335 }
336 ranges.sort_by_key(|(r, _)| (r.start, r.end));
337
338 for w in ranges.windows(2) {
340 let (a, _) = &w[0];
341 let (b, _) = &w[1];
342 if a.end > b.start {
343 return Err(ValueMeshError::InvalidRankCardinality {
345 expected: n,
346 actual: b.start, });
348 }
349 }
350
351 let mut index: HashMap<T, u32> = HashMap::with_capacity(1 + ranges.len());
353 let mut next_id: u32 = 0;
354
355 let id_of = |v: T, index: &mut HashMap<T, u32>, next_id: &mut u32| -> u32 {
358 match index.entry(v) {
359 Entry::Occupied(o) => *o.get(),
360 Entry::Vacant(vac) => {
361 let id = *next_id;
362 *next_id += 1;
363 vac.insert(id);
364 id
365 }
366 }
367 };
368
369 let default_id = id_of(default, &mut index, &mut next_id);
370
371 let mut runs: Vec<Run> = Vec::with_capacity(1 + 2 * ranges.len());
372 let mut cursor = 0usize;
373
374 for (r, v) in ranges.into_iter() {
375 if cursor < r.start {
377 runs.push(Run::new(cursor, r.start, default_id));
378 }
379 let id = id_of(v, &mut index, &mut next_id);
381 runs.push(Run::new(r.start, r.end, id));
382 cursor = r.end;
383 }
384
385 if cursor < n {
387 runs.push(Run::new(cursor, n, default_id));
388 }
389
390 let mut table_slots: Vec<Option<T>> = Vec::new();
393 table_slots.resize_with(next_id as usize, || None);
394
395 for (t, id) in index.into_iter() {
396 table_slots[id as usize] = Some(t);
397 }
398
399 let table: Vec<T> = table_slots
400 .into_iter()
401 .map(|o| o.expect("every id must be assigned"))
402 .collect();
403
404 Ok(Self {
405 region,
406 rep: Rep::Compressed { table, runs },
407 })
408 }
409
410 #[allow(clippy::result_large_err)]
440 pub fn from_dense(region: Region, values: Vec<T>) -> std::result::Result<Self, ValueMeshError> {
441 let mut vm = Self::new(region, values)?;
442 vm.compress_adjacent_in_place();
443 Ok(vm)
444 }
445}
446
447impl<F: Future> ValueMesh<F> {
448 pub async fn join(self) -> ValueMesh<F::Output> {
451 let ValueMesh { region, rep } = self;
452
453 match rep {
454 Rep::Dense { values } => {
455 let results = futures::future::join_all(values).await;
456 ValueMesh::new_unchecked(region, results)
457 }
458 Rep::Compressed { .. } => {
459 unreachable!("join() not implemented for compressed meshes")
460 }
461 }
462 }
463}
464
465impl<T, E> ValueMesh<Result<T, E>> {
466 pub fn transpose(self) -> Result<ValueMesh<T>, E> {
469 let ValueMesh { region, rep } = self;
470
471 match rep {
472 Rep::Dense { values } => {
473 let values = values.into_iter().collect::<Result<Vec<T>, E>>()?;
474 Ok(ValueMesh::new_unchecked(region, values))
475 }
476 Rep::Compressed { table, runs } => {
477 let table: Vec<T> = table.into_iter().collect::<Result<Vec<T>, E>>()?;
478 Ok(ValueMesh {
479 region,
480 rep: Rep::Compressed { table, runs },
481 })
482 }
483 }
484 }
485}
486
487impl<T: 'static> view::Ranked for ValueMesh<T> {
488 type Item = T;
489
490 fn region(&self) -> &Region {
493 &self.region
494 }
495
496 fn get(&self, rank: usize) -> Option<&Self::Item> {
508 if rank >= self.region.num_ranks() {
509 return None;
510 }
511
512 match &self.rep {
513 Rep::Dense { values } => values.get(rank),
514
515 Rep::Compressed { table, runs } => {
516 let rank = rank as u64;
517
518 let idx = runs
521 .binary_search_by(|run| {
522 if run.end <= rank {
523 Ordering::Less
524 } else if run.start > rank {
525 Ordering::Greater
526 } else {
527 Ordering::Equal
528 }
529 })
530 .ok()?;
531
532 let id = runs[idx].id as usize;
534 table.get(id)
535 }
536 }
537 }
538}
539
540impl<T: 'static> ValueMesh<T> {
541 pub fn get_by_base_rank(&self, base_rank: usize) -> Option<&T> {
554 if !self.region.slice().contains(base_rank) {
555 return None;
556 }
557 let ordinal = self.region.point_of_base_rank(base_rank).ok()?.rank();
558 self.get(ordinal)
559 }
560}
561
562impl<T: Clone + 'static> view::RankedSliceable for ValueMesh<T> {
563 fn sliced(&self, region: Region) -> Self {
564 debug_assert!(region.is_subset(self.region()), "sliced: not a subset");
565 let ranks: Vec<T> = self
566 .region()
567 .remap(®ion)
568 .unwrap()
569 .map(|index| self.get(index).unwrap().clone())
570 .collect();
571 debug_assert_eq!(
572 region.num_ranks(),
573 ranks.len(),
574 "sliced: cardinality mismatch"
575 );
576 Self::new_unchecked(region, ranks)
577 }
578}
579
580impl<T> view::BuildFromRegion<T> for ValueMesh<T> {
581 type Error = ValueMeshError;
582
583 fn build_dense(region: Region, values: Vec<T>) -> Result<Self, Self::Error> {
584 Self::new(region, values)
585 }
586
587 fn build_dense_unchecked(region: Region, values: Vec<T>) -> Self {
588 Self::new_unchecked(region, values)
589 }
590}
591
592impl<T> view::BuildFromRegionIndexed<T> for ValueMesh<T> {
593 type Error = ValueMeshError;
594
595 fn build_indexed(
596 region: Region,
597 pairs: impl IntoIterator<Item = (usize, T)>,
598 ) -> Result<Self, Self::Error> {
599 let n = region.num_ranks();
600
601 let mut buf: Vec<MaybeUninit<T>> = Vec::with_capacity(n);
607 unsafe {
613 buf.set_len(n);
614 }
615
616 let words = n.div_ceil(64);
618 let mut bits = vec![0u64; words];
619 let mut filled = 0usize;
620
621 struct DropGuard<T> {
627 buf: NonNull<MaybeUninit<T>>,
628 bits: NonNull<u64>,
629 n_elems: usize,
630 n_words: usize,
631 disarm: bool,
632 }
633
634 impl<T> DropGuard<T> {
635 unsafe fn new(buf: &mut [MaybeUninit<T>], bits: &mut [u64]) -> Self {
654 let n_elems = buf.len();
655 let n_words = bits.len();
656 Self {
659 buf: NonNull::new(buf.as_mut_ptr()).unwrap_or_else(NonNull::dangling),
660 bits: NonNull::new(bits.as_mut_ptr()).unwrap_or_else(NonNull::dangling),
661 n_elems,
662 n_words,
663 disarm: false,
664 }
665 }
666
667 #[inline]
668 fn disarm(&mut self) {
669 self.disarm = true;
670 }
671 }
672
673 impl<T> Drop for DropGuard<T> {
674 fn drop(&mut self) {
675 if self.disarm {
676 return;
677 }
678
679 unsafe {
697 let buf_base = self.buf.as_ptr();
698 let bits_base = self.bits.as_ptr();
699
700 for w in 0..self.n_words {
701 let mut word = *bits_base.add(w);
703
704 if w == self.n_words.saturating_sub(1) {
707 let used_bits = self.n_elems.saturating_sub(w * 64);
708 if used_bits < 64 {
709 let mask = if used_bits == 0 {
710 0
711 } else {
712 (1u64 << used_bits) - 1
713 };
714 word &= mask;
715 }
716 }
717
718 while word != 0 {
720 let tz = word.trailing_zeros() as usize;
721 let i = w * 64 + tz;
722 debug_assert!(i < self.n_elems);
723
724 let slot = buf_base.add(i);
725 ptr::drop_in_place((*slot).as_mut_ptr());
727
728 word &= word - 1;
730 }
731 }
732 }
733 }
734 }
735
736 let mut guard = unsafe { DropGuard::new(&mut buf, &mut bits) };
745
746 for (rank, value) in pairs {
747 if rank >= guard.n_elems {
749 return Err(ValueMeshError::InvalidRankCardinality {
750 expected: guard.n_elems,
751 actual: rank + 1,
752 });
753 }
754
755 let w = rank / 64;
757 let b = rank % 64;
758 let mask = 1u64 << b;
759
760 unsafe {
779 let bits_ptr = guard.bits.as_ptr().add(w);
781 let buf_slot = guard.buf.as_ptr().add(rank);
782
783 let word = *bits_ptr;
785
786 if (word & mask) != 0 {
787 core::ptr::drop_in_place((*buf_slot).as_mut_ptr());
789 } else {
792 *bits_ptr = word | mask;
794 filled += 1;
795 }
796
797 (*buf_slot).write(value);
799 }
800 }
801
802 if filled != n {
803 return Err(ValueMeshError::InvalidRankCardinality {
805 expected: n,
806 actual: filled,
807 });
808 }
809
810 guard.disarm();
812
813 let ranks = unsafe {
815 let ptr = buf.as_mut_ptr() as *mut T;
816 let len = buf.len();
817 let cap = buf.capacity();
818 mem::forget(buf);
823 Vec::from_raw_parts(ptr, len, cap)
824 };
825
826 Ok(Self::new_unchecked(region, ranks))
827 }
828}
829
830impl<T: PartialEq> ValueMesh<T> {
831 pub fn compress_adjacent_in_place(&mut self) {
851 self.compress_adjacent_in_place_by(|a, b| a == b)
852 }
853}
854
855impl<T: Clone + PartialEq> ValueMesh<T> {
856 fn materialized_runs(&self) -> Vec<(Range<usize>, T)> {
872 match &self.rep {
873 Rep::Dense { values } => {
874 let mut out = Vec::new();
876 if values.is_empty() {
877 return out;
878 }
879 let mut start = 0usize;
880 for i in 1..values.len() {
881 if values[i] != values[i - 1] {
882 out.push((start..i, values[i - 1].clone()));
883 start = i;
884 }
885 }
886 out.push((start..values.len(), values.last().unwrap().clone()));
887 out
888 }
889 Rep::Compressed { table, runs } => runs
890 .iter()
891 .map(|r| {
892 let id = r.id as usize;
893 ((r.start as usize..r.end as usize), table[id].clone())
894 })
895 .collect(),
896 }
897 }
898}
899
900impl<T: Clone + Eq> ValueMesh<T> {
901 pub fn merge_from_overlay(&mut self, overlay: ValueOverlay<T>) -> Result<(), BuildError> {
907 let n = self.region.num_ranks();
908
909 for (r, _) in overlay.runs() {
912 if r.end > n {
913 return Err(BuildError::OutOfBounds {
914 range: r.clone(),
915 region_len: n,
916 });
917 }
918 }
919
920 let left = self.materialized_runs();
922 let right: Vec<(std::ops::Range<usize>, T)> = overlay.runs().cloned().collect();
925
926 let merged = rle::merge_value_runs(left, right);
929
930 let (table, raw_runs) = rle::rle_from_value_runs(merged);
932 let runs = raw_runs
933 .into_iter()
934 .map(|(r, id)| Run::new(r.start, r.end, id))
935 .collect();
936 self.rep = Rep::Compressed { table, runs };
937
938 Ok(())
939 }
940}
941
942impl<T> ValueMesh<T> {
943 pub fn compress_adjacent_in_place_by<F>(&mut self, same: F)
965 where
966 F: FnMut(&T, &T) -> bool,
967 {
968 let values = match &mut self.rep {
969 Rep::Dense { values } => std::mem::take(values),
970 Rep::Compressed { .. } => return,
971 };
972 let (table, raw_runs) = rle::rle_from_dense(values, same);
973 let runs = raw_runs
974 .into_iter()
975 .map(|(r, id)| Run::new(r.start, r.end, id))
976 .collect();
977 self.rep = Rep::Compressed { table, runs };
978 }
979}
980
981impl<T> Accumulator for ValueMesh<T>
1000where
1001 T: Eq + Clone + Named,
1002{
1003 type State = Self;
1004 type Update = ValueOverlay<T>;
1005
1006 fn accumulate(&self, state: &mut Self::State, update: Self::Update) -> anyhow::Result<()> {
1007 if state.region().num_ranks() == 0 {
1012 *state = self.clone();
1013 }
1014
1015 state.merge_from_overlay(update)?;
1018 Ok(())
1019 }
1020
1021 fn reducer_spec(&self) -> Option<ReducerSpec> {
1022 Some(ReducerSpec {
1023 typehash: <ValueOverlayReducer<T> as Named>::typehash(),
1024 builder_params: None,
1025 })
1026 }
1027}
1028
1029#[derive(Named)]
1036pub struct ValueOverlayReducer<T>(PhantomData<T>);
1037
1038impl<T> ValueOverlayReducer<T> {
1039 pub fn new() -> Self {
1041 Self(PhantomData)
1042 }
1043}
1044
1045impl<T> CommReducer for ValueOverlayReducer<T>
1055where
1056 T: Eq + Clone + Named,
1057{
1058 type Update = ValueOverlay<T>;
1059
1060 fn reduce(&self, left: Self::Update, right: Self::Update) -> anyhow::Result<Self::Update> {
1062 let merged = rle::merge_value_runs(
1063 left.runs().cloned().collect(),
1064 right.runs().cloned().collect(),
1065 );
1066 Ok(ValueOverlay::try_from_runs(merged)?)
1067 }
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 use std::convert::Infallible;
1073 use std::future::Future;
1074 use std::pin::Pin;
1075 use std::task::Context;
1076 use std::task::Poll;
1077 use std::task::RawWaker;
1078 use std::task::RawWakerVTable;
1079 use std::task::Waker;
1080
1081 use futures::executor::block_on;
1082 use futures::future;
1083 use ndslice::extent;
1084 use ndslice::strategy::gen_region;
1085 use ndslice::view::CollectExactMeshExt;
1086 use ndslice::view::CollectIndexedMeshExt;
1087 use ndslice::view::CollectMeshExt;
1088 use ndslice::view::MapIntoExt;
1089 use ndslice::view::Ranked;
1090 use ndslice::view::RankedSliceable;
1091 use ndslice::view::ViewExt;
1092 use proptest::prelude::*;
1093 use proptest::strategy::ValueTree;
1094 use serde::Deserialize;
1095 use serde::Serialize;
1096 use serde_json;
1097 use typeuri::Named;
1098
1099 use super::*;
1100
1101 #[test]
1102 fn value_mesh_new_ok() {
1103 let region: Region = extent!(replica = 2, gpu = 3).into();
1104 let mesh = ValueMesh::new(region.clone(), (0..6).collect()).expect("new should succeed");
1105 assert_eq!(mesh.region().num_ranks(), 6);
1106 assert_eq!(mesh.values().count(), 6);
1107 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 1, 2, 3, 4, 5]);
1108 }
1109
1110 #[test]
1111 fn get_by_base_rank_resolves_through_region_geometry() {
1112 let region: Region = extent!(row = 4, col = 2).into();
1115 let mesh: ValueMesh<i32> =
1116 ValueMesh::new(region.clone(), (0..8).map(|i| i * 10).collect()).unwrap();
1117 assert_eq!(mesh.get_by_base_rank(0), Some(&0));
1118 assert_eq!(mesh.get_by_base_rank(5), Some(&50));
1119 assert_eq!(mesh.get_by_base_rank(8), None);
1120
1121 let sub = mesh.sliced(region.range("row", ndslice::Range(1, Some(3), 1)).unwrap());
1124
1125 assert_eq!(sub.get_by_base_rank(2), Some(&20)); assert_eq!(sub.get_by_base_rank(5), Some(&50)); assert_eq!(sub.get_by_base_rank(0), None); assert_eq!(sub.get_by_base_rank(6), None); assert_eq!(sub.get(0), Some(&20)); }
1134
1135 #[test]
1136 fn value_mesh_new_len_mismatch_is_error() {
1137 let region: Region = extent!(replica = 2, gpu = 3).into();
1138 let err = ValueMesh::new(region, vec![0_i32; 5]).unwrap_err();
1139 let ValueMeshError::InvalidRankCardinality { expected, actual } = err;
1140 assert_eq!(expected, 6);
1141 assert_eq!(actual, 5);
1142 }
1143
1144 #[test]
1145 fn value_mesh_transpose_ok_and_err() {
1146 let region: Region = extent!(x = 2).into();
1147
1148 let ok_mesh = ValueMesh::new(region.clone(), vec![Ok::<_, Infallible>(1), Ok(2)]).unwrap();
1150 let ok = ok_mesh.transpose().unwrap();
1151 assert_eq!(ok.values().collect::<Vec<_>>(), vec![1, 2]);
1152
1153 #[derive(Debug, PartialEq)]
1155 enum E {
1156 Boom,
1157 }
1158 let err_mesh = ValueMesh::new(region, vec![Ok(1), Err(E::Boom)]).unwrap();
1159 let err = err_mesh.transpose().unwrap_err();
1160 assert_eq!(err, E::Boom);
1161 }
1162
1163 #[test]
1164 fn value_mesh_join_preserves_region_and_values() {
1165 let region: Region = extent!(x = 2, y = 2).into();
1166 let futs = vec![
1167 future::ready(10),
1168 future::ready(11),
1169 future::ready(12),
1170 future::ready(13),
1171 ];
1172 let mesh = ValueMesh::new(region.clone(), futs).unwrap();
1173
1174 let joined = block_on(mesh.join());
1175 assert_eq!(joined.region().num_ranks(), 4);
1176 assert_eq!(joined.values().collect::<Vec<_>>(), vec![10, 11, 12, 13]);
1177 }
1178
1179 #[test]
1180 fn collect_mesh_ok() {
1181 let region: Region = extent!(x = 2, y = 3).into();
1182 let mesh = (0..6)
1183 .collect_mesh::<ValueMesh<_>>(region.clone())
1184 .expect("collect_mesh should succeed");
1185
1186 assert_eq!(mesh.region().num_ranks(), 6);
1187 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 1, 2, 3, 4, 5]);
1188 }
1189
1190 #[test]
1191 fn collect_mesh_len_too_short_is_error() {
1192 let region: Region = extent!(x = 2, y = 3).into();
1193 let err = (0..5).collect_mesh::<ValueMesh<_>>(region).unwrap_err();
1194
1195 let ValueMeshError::InvalidRankCardinality { expected, actual } = err;
1196 assert_eq!(expected, 6);
1197 assert_eq!(actual, 5);
1198 }
1199
1200 #[test]
1201 fn collect_mesh_len_too_long_is_error() {
1202 let region: Region = extent!(x = 2, y = 3).into();
1203 let err = (0..7).collect_mesh::<ValueMesh<_>>(region).unwrap_err();
1204 let ValueMeshError::InvalidRankCardinality { expected, actual } = err;
1205 assert_eq!(expected, 6);
1206 assert_eq!(actual, 7);
1207 }
1208
1209 #[test]
1210 fn collect_mesh_from_map_pipeline() {
1211 let region: Region = extent!(x = 2, y = 2).into();
1212 let mesh = (0..4)
1213 .map(|i| i * 10)
1214 .collect_mesh::<ValueMesh<_>>(region.clone())
1215 .unwrap();
1216
1217 assert_eq!(mesh.region().num_ranks(), 4);
1218 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 10, 20, 30]);
1219 }
1220
1221 #[test]
1222 fn collect_exact_mesh_ok() {
1223 let region: Region = extent!(x = 2, y = 3).into();
1224 let mesh = (0..6)
1225 .collect_exact_mesh::<ValueMesh<_>>(region.clone())
1226 .expect("collect_exact_mesh should succeed");
1227
1228 assert_eq!(mesh.region().num_ranks(), 6);
1229 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 1, 2, 3, 4, 5]);
1230 }
1231
1232 #[test]
1233 fn collect_exact_mesh_len_too_short_is_error() {
1234 let region: Region = extent!(x = 2, y = 3).into();
1235 let err = (0..5)
1236 .collect_exact_mesh::<ValueMesh<_>>(region)
1237 .unwrap_err();
1238
1239 let ValueMeshError::InvalidRankCardinality { expected, actual } = err;
1240 assert_eq!(expected, 6);
1241 assert_eq!(actual, 5);
1242 }
1243
1244 #[test]
1245 fn collect_exact_mesh_len_too_long_is_error() {
1246 let region: Region = extent!(x = 2, y = 3).into();
1247 let err = (0..7)
1248 .collect_exact_mesh::<ValueMesh<_>>(region)
1249 .unwrap_err();
1250
1251 let ValueMeshError::InvalidRankCardinality { expected, actual } = err;
1252 assert_eq!(expected, 6);
1253 assert_eq!(actual, 7);
1254 }
1255
1256 #[test]
1257 fn collect_exact_mesh_from_map_pipeline() {
1258 let region: Region = extent!(x = 2, y = 2).into();
1259 let mesh = (0..4)
1260 .map(|i| i * 10)
1261 .collect_exact_mesh::<ValueMesh<_>>(region.clone())
1262 .unwrap();
1263
1264 assert_eq!(mesh.region().num_ranks(), 4);
1265 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 10, 20, 30]);
1266 }
1267
1268 #[test]
1269 fn collect_indexed_ok_shuffled() {
1270 let region: Region = extent!(x = 2, y = 3).into();
1271 let pairs = vec![(3, 30), (0, 0), (5, 50), (2, 20), (1, 10), (4, 40)];
1273 let mesh = pairs
1274 .into_iter()
1275 .collect_indexed::<ValueMesh<_>>(region.clone())
1276 .unwrap();
1277
1278 assert_eq!(mesh.region().num_ranks(), 6);
1279 assert_eq!(
1280 mesh.values().collect::<Vec<_>>(),
1281 vec![0, 10, 20, 30, 40, 50]
1282 );
1283 }
1284
1285 #[test]
1286 fn collect_indexed_missing_rank_is_error() {
1287 let region: Region = extent!(x = 2, y = 2).into(); let pairs = vec![(0, 100), (1, 101), (2, 102)];
1290 let err = pairs
1291 .into_iter()
1292 .collect_indexed::<ValueMesh<_>>(region)
1293 .unwrap_err();
1294
1295 let ValueMeshError::InvalidRankCardinality { expected, actual } = err;
1296 assert_eq!(expected, 4);
1297 assert_eq!(actual, 3); }
1299
1300 #[test]
1301 fn collect_indexed_out_of_bounds_is_error() {
1302 let region: Region = extent!(x = 2, y = 2).into(); let pairs = vec![(0, 1), (4, 9)]; let err = pairs
1305 .into_iter()
1306 .collect_indexed::<ValueMesh<_>>(region)
1307 .unwrap_err();
1308
1309 let ValueMeshError::InvalidRankCardinality { expected, actual } = err;
1310 assert_eq!(expected, 4);
1311 assert_eq!(actual, 5); }
1313
1314 #[test]
1315 fn collect_indexed_duplicate_last_write_wins() {
1316 let region: Region = extent!(x = 1, y = 3).into(); let pairs = vec![(0, 7), (1, 8), (1, 88), (2, 9)];
1319 let mesh = pairs
1320 .into_iter()
1321 .collect_indexed::<ValueMesh<_>>(region.clone())
1322 .unwrap();
1323
1324 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![7, 88, 9]);
1325 }
1326
1327 #[allow(clippy::result_large_err)]
1329 fn build_value_mesh_indexed<T>(
1330 region: Region,
1331 pairs: impl IntoIterator<Item = (usize, T)>,
1332 ) -> std::result::Result<ValueMesh<T>, ValueMeshError> {
1333 let n = region.num_ranks();
1334
1335 let mut buf: Vec<Option<T>> = std::iter::repeat_with(|| None).take(n).collect();
1337 let mut filled = 0usize;
1338
1339 for (rank, value) in pairs {
1340 if rank >= n {
1341 return Err(ValueMeshError::InvalidRankCardinality {
1345 expected: n,
1346 actual: rank + 1,
1347 });
1348 }
1349 if buf[rank].is_none() {
1350 filled += 1;
1351 }
1352 buf[rank] = Some(value); }
1354
1355 if filled != n {
1356 return Err(ValueMeshError::InvalidRankCardinality {
1358 expected: n,
1359 actual: filled,
1360 });
1361 }
1362
1363 let ranks: Vec<T> = buf.into_iter().map(Option::unwrap).collect();
1365 Ok(ValueMesh::new_unchecked(region, ranks))
1366 }
1367
1368 fn hash_key(x: usize) -> u64 {
1376 let mut z = x as u64 ^ 0x9E3779B97F4A7C15;
1377 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
1378 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
1379 z ^ (z >> 31)
1380 }
1381
1382 fn pseudo_shuffle<'a, T: 'a>(v: &'a mut [T], key: impl Fn(usize) -> u64 + Copy) {
1399 let mut with_keys: Vec<(u64, usize)> = (0..v.len()).map(|i| (key(i), i)).collect();
1401 with_keys.sort_by_key(|&(k, _)| k);
1402 let perm: Vec<usize> = with_keys.into_iter().map(|(_, i)| i).collect();
1403
1404 let mut seen = vec![false; v.len()];
1407 for i in 0..v.len() {
1408 if seen[i] {
1409 continue;
1410 }
1411 let mut a = i;
1412 while !seen[a] {
1413 seen[a] = true;
1414 let b = perm[a];
1415 if b == i {
1417 break;
1418 }
1419 v.swap(a, b);
1420 a = b;
1421 }
1422 }
1423 }
1424
1425 proptest! {
1441 #[test]
1442 fn try_collect_opt_equivalence(region in gen_region(1..=4, 6), extra_len in 0usize..=12) {
1443 let n = region.num_ranks();
1444
1445 let mut pairs: Vec<(usize, i64)> = (0..n).map(|r| (r, r as i64)).collect();
1447
1448 let extras = proptest::collection::vec(0..n, extra_len)
1451 .new_tree(&mut proptest::test_runner::TestRunner::default())
1452 .unwrap()
1453 .current();
1454 for (k, r) in extras.into_iter().enumerate() {
1455 pairs.push((r, (n as i64) + (k as i64)));
1456 }
1457
1458 pseudo_shuffle(&mut pairs, hash_key);
1461
1462 let mesh_ref = build_value_mesh_indexed(region.clone(), pairs.clone()).unwrap();
1464 let mesh_opt = pairs.into_iter().collect_indexed::<ValueMesh<_>>(region.clone()).unwrap();
1465
1466 prop_assert_eq!(mesh_ref.region(), mesh_opt.region());
1467 prop_assert_eq!(mesh_ref.values().collect::<Vec<_>>(), mesh_opt.values().collect::<Vec<_>>());
1468 }
1469 }
1470
1471 proptest! {
1482 #[test]
1483 fn try_collect_opt_missing_rank_errors_match(region in gen_region(1..=4, 6)) {
1484 let n = region.num_ranks();
1485 let mut pairs: Vec<(usize, i64)> = (0..n).map(|r| (r, r as i64)).collect();
1487 if n > 0 {
1489 let drop_idx = 0usize; pairs.remove(drop_idx);
1491 }
1492 pseudo_shuffle(&mut pairs, hash_key);
1494
1495 let ref_err = build_value_mesh_indexed(region.clone(), pairs.clone()).unwrap_err();
1496 let opt_err = pairs.into_iter().collect_indexed::<ValueMesh<_>>(region).unwrap_err();
1497 assert_eq!(format!("{ref_err:?}"), format!("{opt_err:?}"));
1498 }
1499 }
1500
1501 proptest! {
1513 #[test]
1514 fn try_collect_opt_out_of_bound_errors_match(region in gen_region(1..=4, 6)) {
1515 let n = region.num_ranks();
1516 let mut pairs = vec![(0usize, 0i64), (n, 123i64)];
1518 pseudo_shuffle(&mut pairs, hash_key);
1519
1520 let ref_err = build_value_mesh_indexed(region.clone(), pairs.clone()).unwrap_err();
1521 let opt_err = pairs.into_iter().collect_indexed::<ValueMesh<_>>(region).unwrap_err();
1522 assert_eq!(format!("{ref_err:?}"), format!("{opt_err:?}"));
1523 }
1524 }
1525
1526 #[test]
1527 fn map_into_preserves_region_and_order() {
1528 let region: Region = extent!(rows = 2, cols = 3).into();
1529 let vm = ValueMesh::new_unchecked(region.clone(), vec![0, 1, 2, 3, 4, 5]);
1530
1531 let doubled: ValueMesh<_> = vm.map_into(|x| x * 2);
1532 assert_eq!(doubled.region, region);
1533 assert_eq!(
1534 doubled.values().collect::<Vec<_>>(),
1535 vec![0, 2, 4, 6, 8, 10]
1536 );
1537 }
1538
1539 #[test]
1540 fn map_into_ref_borrows_and_preserves() {
1541 let region: Region = extent!(n = 4).into();
1542 let vm = ValueMesh::new_unchecked(
1543 region.clone(),
1544 vec!["a".to_string(), "b".into(), "c".into(), "d".into()],
1545 );
1546
1547 let lens: ValueMesh<_> = vm.map_into(|s| s.len());
1548 assert_eq!(lens.region, region);
1549 assert_eq!(lens.values().collect::<Vec<_>>(), vec![1, 1, 1, 1]);
1550 }
1551
1552 #[test]
1553 fn try_map_into_short_circuits_on_error() {
1554 let region = extent!(n = 4).into();
1555 let vm = ValueMesh::new_unchecked(region, vec![1, 2, 3, 4]);
1556
1557 let res: Result<ValueMesh<i32>, &'static str> =
1558 vm.try_map_into(|x| if x == &3 { Err("boom") } else { Ok(x + 10) });
1559
1560 assert!(res.is_err());
1561 assert_eq!(res.unwrap_err(), "boom");
1562 }
1563
1564 #[test]
1565 fn try_map_into_ref_short_circuits_on_error() {
1566 let region = extent!(n = 4).into();
1567 let vm = ValueMesh::new_unchecked(region, vec![1, 2, 3, 4]);
1568
1569 let res: Result<ValueMesh<i32>, &'static str> =
1570 vm.try_map_into(|x| if x == &3 { Err("boom") } else { Ok(x + 10) });
1571
1572 assert!(res.is_err());
1573 assert_eq!(res.unwrap_err(), "boom");
1574 }
1575
1576 fn noop_waker() -> Waker {
1578 fn clone(_: *const ()) -> RawWaker {
1579 RawWaker::new(std::ptr::null(), &VTABLE)
1580 }
1581 fn wake(_: *const ()) {}
1582 fn wake_by_ref(_: *const ()) {}
1583 fn drop(_: *const ()) {}
1584 static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
1585 unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }
1590 }
1591
1592 fn poll_now<F: Future>(mut fut: F) -> F::Output {
1593 let waker = noop_waker();
1594 let mut cx = Context::from_waker(&waker);
1595 let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
1600 match fut.as_mut().poll(&mut cx) {
1601 Poll::Ready(v) => v,
1602 Poll::Pending => unreachable!("Ready futures must complete immediately"),
1603 }
1604 }
1605 #[test]
1608 fn map_into_ready_futures() {
1609 let region: Region = extent!(r = 2, c = 2).into();
1610 let vm = ValueMesh::new_unchecked(region.clone(), vec![10, 20, 30, 40]);
1611
1612 let pending: ValueMesh<core::future::Ready<_>> =
1614 vm.map_into(|x| core::future::ready(x + 1));
1615 assert_eq!(pending.region, region);
1616
1617 let results: Vec<_> = pending.values().map(|f| poll_now(f.clone())).collect();
1619 assert_eq!(results, vec![11, 21, 31, 41]);
1620 }
1621
1622 #[test]
1623 fn map_into_single_element_mesh() {
1624 let region: Region = extent!(n = 1).into();
1625 let vm = ValueMesh::new_unchecked(region.clone(), vec![7]);
1626
1627 let out: ValueMesh<_> = vm.map_into(|x| x * x);
1628 assert_eq!(out.region, region);
1629 assert_eq!(out.values().collect::<Vec<_>>(), vec![49]);
1630 }
1631
1632 #[test]
1633 fn map_into_ref_with_non_clone_field() {
1634 #[derive(Debug, PartialEq, Eq)]
1636 struct NotClone(i32);
1637
1638 let region: Region = extent!(x = 3).into();
1639 let values = vec![(10, NotClone(1)), (20, NotClone(2)), (30, NotClone(3))];
1640 let mesh: ValueMesh<(i32, NotClone)> =
1641 values.into_iter().collect_mesh(region.clone()).unwrap();
1642
1643 let projected: ValueMesh<i32> = mesh.map_into(|t| t.0);
1644 assert_eq!(projected.values().collect::<Vec<_>>(), vec![10, 20, 30]);
1645 assert_eq!(projected.region(), ®ion);
1646 }
1647
1648 #[test]
1649 fn rle_roundtrip_all_equal() {
1650 let region: Region = extent!(n = 6).into();
1651 let mut vm = ValueMesh::new_unchecked(region.clone(), vec![42; 6]);
1652
1653 vm.compress_adjacent_in_place();
1655 let collected: Vec<_> = vm.values().collect();
1656 assert_eq!(collected, vec![42, 42, 42, 42, 42, 42]);
1657
1658 for i in 0..region.num_ranks() {
1660 assert_eq!(vm.get(i), Some(&42));
1661 }
1662 assert_eq!(vm.get(region.num_ranks()), None); }
1664
1665 #[test]
1666 fn rle_roundtrip_alternating() {
1667 let region: Region = extent!(n = 6).into();
1668 let original = vec![1, 2, 1, 2, 1, 2];
1669 let mut vm = ValueMesh::new_unchecked(region.clone(), original.clone());
1670
1671 vm.compress_adjacent_in_place();
1672 let collected: Vec<_> = vm.values().collect();
1673 assert_eq!(collected, original);
1674
1675 assert_eq!(vm.get(0), Some(&1));
1677 assert_eq!(vm.get(1), Some(&2));
1678 assert_eq!(vm.get(3), Some(&2));
1679 assert_eq!(vm.get(5), Some(&2));
1680 }
1681
1682 #[test]
1683 fn rle_roundtrip_blocky_and_slice() {
1684 let region: Region = extent!(n = 10).into();
1686 let original = vec![0, 0, 0, 1, 1, 2, 2, 2, 2, 3];
1687 let mut vm = ValueMesh::new_unchecked(region.clone(), original.clone());
1688
1689 vm.compress_adjacent_in_place();
1690 let collected: Vec<_> = vm.values().collect();
1691 assert_eq!(collected, original);
1692
1693 let sub_region = region.range("n", 3..8).unwrap();
1695 let sliced = vm.sliced(sub_region);
1696 let sliced_vec: Vec<_> = sliced.values().collect();
1697 assert_eq!(sliced_vec, vec![1, 1, 2, 2, 2]);
1698 }
1699
1700 #[test]
1701 fn rle_idempotent_noop_on_second_call() {
1702 let region: Region = extent!(n = 7).into();
1703 let original = vec![9, 9, 9, 8, 8, 9, 9];
1704 let mut vm = ValueMesh::new_unchecked(region.clone(), original.clone());
1705
1706 vm.compress_adjacent_in_place();
1707 let once: Vec<_> = vm.values().collect();
1708 assert_eq!(once, original);
1709
1710 vm.compress_adjacent_in_place();
1713 let twice: Vec<_> = vm.values().collect();
1714 assert_eq!(twice, original);
1715 }
1716
1717 #[test]
1718 fn rle_works_after_build_indexed() {
1719 let region: Region = extent!(x = 2, y = 3).into(); let pairs = vec![(3, 30), (0, 0), (5, 50), (2, 20), (1, 10), (4, 40)];
1723 let mut vm = pairs
1724 .into_iter()
1725 .collect_indexed::<ValueMesh<_>>(region.clone())
1726 .unwrap();
1727
1728 vm.compress_adjacent_in_place();
1731 let collected: Vec<_> = vm.values().collect();
1732 assert_eq!(collected, vec![0, 10, 20, 30, 40, 50]);
1733 assert_eq!(vm.get(4), Some(&40));
1735 }
1736
1737 #[test]
1738 fn rle_handles_singleton_mesh() {
1739 let region: Region = extent!(n = 1).into();
1740 let mut vm = ValueMesh::new_unchecked(region.clone(), vec![123]);
1741
1742 vm.compress_adjacent_in_place();
1743 let collected: Vec<_> = vm.values().collect();
1744 assert_eq!(collected, vec![123]);
1745 assert_eq!(vm.get(0), Some(&123));
1746 assert_eq!(vm.get(1), None);
1747 }
1748
1749 #[test]
1750 fn test_dense_round_trip_json() {
1751 let region: Region = extent!(x = 5).into();
1753 let dense = ValueMesh::new(region.clone(), vec![1, 2, 3, 4, 5]).unwrap();
1754
1755 let json = serde_json::to_string_pretty(&dense).unwrap();
1756 let restored: ValueMesh<i32> = serde_json::from_str(&json).unwrap();
1757
1758 assert_eq!(dense, restored);
1759
1760 let v: serde_json::Value = serde_json::from_str(&json).unwrap();
1763 let tag = v.get("rep").and_then(|o| o.get("Dense"));
1765 assert!(tag.is_some(), "json is {}", json);
1766 }
1767
1768 #[test]
1769 fn test_dense_round_trip_bincode() {
1770 let region: Region = extent!(x = 5).into();
1772 let dense = ValueMesh::new(region.clone(), vec![1, 2, 3, 4, 5]).unwrap();
1773
1774 let encoded = bincode::serde::encode_to_vec(&dense, bincode::config::legacy()).unwrap();
1775 let restored: ValueMesh<i32> =
1776 bincode::serde::decode_from_slice(&encoded, bincode::config::legacy())
1777 .map(|(v, _)| v)
1778 .unwrap();
1779
1780 assert_eq!(dense, restored);
1781 assert!(matches!(restored.rep, Rep::Dense { .. }));
1783 }
1784
1785 #[test]
1786 fn test_compressed_round_trip_json() {
1787 let region: Region = extent!(x = 10).into();
1790 let mut mesh = ValueMesh::new(region.clone(), vec![1, 1, 1, 2, 2, 3, 3, 3, 3, 3]).unwrap();
1791 mesh.compress_adjacent_in_place();
1792
1793 let json = serde_json::to_string_pretty(&mesh).unwrap();
1794 let restored: ValueMesh<i32> = serde_json::from_str(&json).unwrap();
1795
1796 assert_eq!(mesh, restored);
1798
1799 let v: serde_json::Value = serde_json::from_str(&json).unwrap();
1801 let tag = v.get("rep").and_then(|o| o.get("Compressed"));
1803 assert!(tag.is_some(), "json is {}", json);
1804 }
1805
1806 #[test]
1807 fn test_compressed_round_trip_bincode() {
1808 let region: Region = extent!(x = 10).into();
1811 let mut mesh = ValueMesh::new(region.clone(), vec![1, 1, 1, 2, 2, 3, 3, 3, 3, 3]).unwrap();
1812 mesh.compress_adjacent_in_place();
1813
1814 let encoded = bincode::serde::encode_to_vec(&mesh, bincode::config::legacy()).unwrap();
1815 let restored: ValueMesh<i32> =
1816 bincode::serde::decode_from_slice(&encoded, bincode::config::legacy())
1817 .map(|(v, _)| v)
1818 .unwrap();
1819
1820 assert_eq!(mesh, restored);
1822 assert!(matches!(restored.rep, Rep::Compressed { .. }));
1824 }
1825
1826 #[test]
1827 fn test_stable_run_encoding() {
1828 let run = Run::new(0, 10, 42);
1829 let json = serde_json::to_string(&run).unwrap();
1830 let decoded: Run = serde_json::from_str(&json).unwrap();
1831
1832 assert_eq!(run, decoded);
1833 assert_eq!(run.start, 0);
1834 assert_eq!(run.end, 10);
1835 assert_eq!(run.id, 42);
1836
1837 let (range, id): (Range<usize>, u32) = run.try_into().unwrap();
1839 assert_eq!(range, 0..10);
1840 assert_eq!(id, 42);
1841 }
1842
1843 #[test]
1844 fn from_single_builds_single_run() {
1845 let region: Region = extent!(n = 6).into();
1846 let vm = ValueMesh::from_single(region.clone(), 7);
1847
1848 assert_eq!(vm.region(), ®ion);
1849 assert_eq!(vm.values().collect::<Vec<_>>(), vec![7, 7, 7, 7, 7, 7]);
1850 assert_eq!(vm.get(0), Some(&7));
1851 assert_eq!(vm.get(5), Some(&7));
1852 assert_eq!(vm.get(6), None);
1853 }
1854
1855 #[test]
1856 fn from_default_builds_with_default_value() {
1857 let region: Region = extent!(n = 6).into();
1858 let vm = ValueMesh::<i32>::from_default(region.clone());
1859
1860 assert_eq!(vm.region(), ®ion);
1861 assert_eq!(vm.values().collect::<Vec<_>>(), vec![0, 0, 0, 0, 0, 0]);
1863 assert_eq!(vm.get(0), Some(&0));
1864 assert_eq!(vm.get(5), Some(&0));
1865 }
1866
1867 #[test]
1868 fn test_default_vs_single_equivalence() {
1869 let region: Region = extent!(x = 4).into();
1870 let d1 = ValueMesh::<i32>::from_default(region.clone());
1871 let d2 = ValueMesh::from_single(region.clone(), 0);
1872 assert_eq!(d1, d2);
1873 }
1874
1875 #[test]
1876 fn build_from_ranges_with_default_basic() {
1877 let region: Region = extent!(n = 10).into();
1878 let vm = ValueMesh::from_ranges_with_default(
1879 region.clone(),
1880 0, vec![(2..4, 1), (6..9, 2)],
1882 )
1883 .unwrap();
1884
1885 assert_eq!(vm.region(), ®ion);
1886 assert_eq!(
1887 vm.values().collect::<Vec<_>>(),
1888 vec![0, 0, 1, 1, 0, 0, 2, 2, 2, 0]
1889 );
1890
1891 if let Rep::Compressed { table, runs } = &vm.rep {
1894 assert!(table.len() <= 3);
1896 assert_eq!(runs.len(), 5);
1897 } else {
1898 panic!("expected compressed");
1899 }
1900 }
1901
1902 #[test]
1903 fn build_from_ranges_with_default_edge_cases() {
1904 let region: Region = extent!(n = 5).into();
1905
1906 let vm = ValueMesh::from_ranges_with_default(region.clone(), 9, vec![(0..5, 3)]).unwrap();
1908 assert_eq!(vm.values().collect::<Vec<_>>(), vec![3, 3, 3, 3, 3]);
1909
1910 let vm = ValueMesh::from_ranges_with_default(region.clone(), 0, vec![(1..2, 7), (2..4, 7)])
1912 .unwrap();
1913 assert_eq!(vm.values().collect::<Vec<_>>(), vec![0, 7, 7, 7, 0]);
1914
1915 let empty_region: Region = extent!(n = 0).into();
1917 let vm = ValueMesh::from_ranges_with_default(empty_region.clone(), 42, vec![]).unwrap();
1918 assert_eq!(vm.values().collect::<Vec<_>>(), Vec::<i32>::new());
1919 }
1920
1921 #[test]
1922 fn from_dense_builds_and_compresses() {
1923 let region: Region = extent!(n = 6).into();
1924 let mesh = ValueMesh::from_dense(region.clone(), vec![1, 1, 2, 2, 3, 3]).unwrap();
1925
1926 assert_eq!(mesh.region(), ®ion);
1927 assert!(matches!(mesh.rep, Rep::Compressed { .. }));
1928 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![1, 1, 2, 2, 3, 3]);
1929
1930 assert_eq!(mesh.get(0), Some(&1));
1932 assert_eq!(mesh.get(3), Some(&2));
1933 assert_eq!(mesh.get(5), Some(&3));
1934 }
1935
1936 #[test]
1937 fn merge_from_overlay_basic() {
1938 let region: Region = extent!(n = 8).into();
1940 let mut mesh = ValueMesh::from_dense(region.clone(), vec![1, 1, 1, 2, 2, 2, 3, 3]).unwrap();
1941
1942 let overlay = ValueOverlay::try_from_runs(vec![(2..6, 9)]).unwrap();
1944
1945 mesh.merge_from_overlay(overlay).unwrap();
1946
1947 let out = mesh.materialized_runs();
1949
1950 assert_eq!(out, vec![(0..2, 1), (2..6, 9), (6..8, 3)]);
1952 }
1953
1954 #[test]
1955 fn merge_from_overlay_multiple_spans() {
1956 let region: Region = extent!(m = 12).into();
1958 let mut mesh =
1959 ValueMesh::from_dense(region.clone(), vec![1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])
1960 .unwrap();
1961
1962 let overlay = ValueOverlay::try_from_runs(vec![(2..6, 9), (9..11, 8)]).unwrap();
1965
1966 mesh.merge_from_overlay(overlay).unwrap();
1967 let out = mesh.materialized_runs();
1968
1969 assert_eq!(
1976 out,
1977 vec![(0..2, 1), (2..6, 9), (6..9, 3), (9..11, 8), (11..12, 4)]
1978 );
1979 }
1980
1981 #[test]
1982 fn merge_from_overlay_crosses_row_boundary() {
1983 let region: Region = extent!(rows = 2, cols = 5).into();
1985
1986 let mut mesh =
1990 ValueMesh::from_dense(region.clone(), vec![1, 1, 1, 2, 2, 3, 3, 4, 4, 4]).unwrap();
1991
1992 let overlay = ValueOverlay::try_from_runs(vec![(3..7, 9)]).unwrap();
1997
1998 mesh.merge_from_overlay(overlay).unwrap();
1999
2000 let flat: Vec<_> = mesh.values().collect();
2003 assert_eq!(flat, vec![1, 1, 1, 9, 9, 9, 9, 4, 4, 4]);
2004
2005 let runs = mesh.materialized_runs();
2008 assert_eq!(runs, vec![(0..3, 1), (3..7, 9), (7..10, 4)]);
2009 }
2010
2011 #[test]
2012 fn accumulator_initializes_and_merges_overlays() {
2013 #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Named)]
2016 enum Val {
2017 Init,
2018 Running,
2019 Stopped,
2020 }
2021
2022 let region: Region = extent!(m = 8).into();
2023 let template = ValueMesh::from_single(region.clone(), Val::Init);
2024
2025 let mut state = ValueMesh::<Val>::default();
2027 assert_eq!(state.region().num_ranks(), 0);
2028
2029 let overlay1 = ValueOverlay::try_from_runs(vec![(2..5, Val::Running)]).unwrap();
2031 template.accumulate(&mut state, overlay1).unwrap();
2032
2033 assert_eq!(state.region().num_ranks(), 8);
2035 let vals: Vec<_> = state.values().collect();
2036 assert_eq!(
2037 vals,
2038 vec![
2039 Val::Init,
2040 Val::Init,
2041 Val::Running,
2042 Val::Running,
2043 Val::Running,
2044 Val::Init,
2045 Val::Init,
2046 Val::Init,
2047 ]
2048 );
2049
2050 let overlay2 = ValueOverlay::try_from_runs(vec![(5..7, Val::Stopped)]).unwrap();
2052 template.accumulate(&mut state, overlay2).unwrap();
2053
2054 let vals: Vec<_> = state.values().collect();
2055 assert_eq!(
2056 vals,
2057 vec![
2058 Val::Init,
2059 Val::Init,
2060 Val::Running,
2061 Val::Running,
2062 Val::Running,
2063 Val::Stopped,
2064 Val::Stopped,
2065 Val::Init,
2066 ]
2067 );
2068
2069 let spec = template
2071 .reducer_spec()
2072 .expect("reducer_spec should be Some");
2073 assert_eq!(
2074 spec.typehash,
2075 <ValueOverlayReducer<Val> as Named>::typehash()
2076 );
2077 }
2078
2079 #[test]
2080 fn value_overlay_reducer_merges_with_right_wins() {
2081 #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Named)]
2084 enum Val {
2085 Init,
2086 Running,
2087 Stopped,
2088 }
2089
2090 let reducer = ValueOverlayReducer::<Val>(PhantomData);
2091
2092 let left =
2094 ValueOverlay::try_from_runs(vec![(0..4, Val::Running), (6..8, Val::Stopped)]).unwrap();
2095
2096 let right = ValueOverlay::try_from_runs(vec![(2..6, Val::Init)]).unwrap();
2099
2100 let merged = reducer.reduce(left, right).unwrap();
2101 let runs: Vec<_> = merged.runs().cloned().collect();
2102
2103 assert_eq!(
2108 runs,
2109 vec![
2110 (0..2, Val::Running),
2111 (2..6, Val::Init),
2112 (6..8, Val::Stopped),
2113 ]
2114 );
2115 }
2116}