1use super::csr_edges::CsrEdges;
2use super::vertex::VertexId;
3use formualizer_common::Coord as AbsCoord;
4use rustc_hash::{FxHashMap, FxHashSet};
5
6#[cfg(test)]
7mod tests {
8 use super::*;
9 use formualizer_common::Coord as AbsCoord;
10
11 #[test]
12 fn test_delta_slab_add_edge() {
13 let csr = CsrEdges::from_adjacency(
14 vec![(0u32, vec![1u32])],
15 &[
16 AbsCoord::new(0, 0),
17 AbsCoord::new(0, 1),
18 AbsCoord::new(0, 2),
19 ],
20 );
21 let mut delta = DeltaEdgeSlab::new();
22
23 delta.add_edge(VertexId(0), VertexId(2));
24
25 let merged = delta.merged_view(&csr, VertexId(0));
26 assert_eq!(merged, vec![VertexId(1), VertexId(2)]);
27 }
28
29 #[test]
30 fn test_delta_slab_remove_edge() {
31 let csr = CsrEdges::from_adjacency(
32 vec![(0u32, vec![1u32, 2u32, 3u32])],
33 &[
34 AbsCoord::new(0, 0),
35 AbsCoord::new(0, 1),
36 AbsCoord::new(0, 2),
37 AbsCoord::new(0, 3),
38 ],
39 );
40 let mut delta = DeltaEdgeSlab::new();
41
42 delta.remove_edge(VertexId(0), VertexId(2));
43
44 let merged = delta.merged_view(&csr, VertexId(0));
45 assert_eq!(merged, vec![VertexId(1), VertexId(3)]);
46 }
47
48 #[test]
49 fn test_delta_slab_rebuild_threshold() {
50 let mut edges = CsrMutableEdges::new();
51
52 for i in 0..1000 {
54 edges.add_edge(VertexId(i), VertexId(i + 1));
55 }
56
57 assert!(edges.delta_size() < 100); }
60
61 #[test]
62 fn test_delta_slab_multiple_operations() {
63 let csr = CsrEdges::from_adjacency(
64 vec![(0u32, vec![1u32, 2u32]), (1u32, vec![3u32])],
65 &[
66 AbsCoord::new(0, 0),
67 AbsCoord::new(0, 1),
68 AbsCoord::new(0, 2),
69 AbsCoord::new(1, 0),
70 ],
71 );
72 let mut delta = DeltaEdgeSlab::new();
73
74 delta.add_edge(VertexId(0), VertexId(3));
76 delta.remove_edge(VertexId(0), VertexId(1));
77 delta.add_edge(VertexId(0), VertexId(4));
78
79 let merged = delta.merged_view(&csr, VertexId(0));
80 assert_eq!(merged, vec![VertexId(2), VertexId(3), VertexId(4)]);
81 }
82
83 #[test]
84 fn test_mutable_edges_exact_edge_count_includes_delta() {
85 let mut edges = CsrMutableEdges::with_coords(vec![
86 AbsCoord::new(0, 0),
87 AbsCoord::new(0, 1),
88 AbsCoord::new(0, 2),
89 ]);
90 edges.add_edge(VertexId(0), VertexId(1));
91 edges.add_edge(VertexId(0), VertexId(2));
92 assert_eq!(edges.num_edges_exact(), 2);
93
94 edges.remove_edge(VertexId(0), VertexId(1));
95 assert_eq!(edges.num_edges_exact(), 1);
96 }
97
98 #[test]
99 fn test_delta_slab_empty_base() {
100 let csr = CsrEdges::empty();
101 let mut delta = DeltaEdgeSlab::new();
102
103 delta.add_edge(VertexId(0), VertexId(1));
104 delta.add_edge(VertexId(0), VertexId(2));
105
106 let merged = delta.merged_view(&csr, VertexId(0));
107 assert_eq!(merged, vec![VertexId(1), VertexId(2)]);
108 }
109
110 #[test]
111 fn test_delta_slab_remove_nonexistent() {
112 let csr = CsrEdges::from_adjacency(
113 vec![(0u32, vec![1u32])],
114 &[AbsCoord::new(0, 0), AbsCoord::new(0, 1)],
115 );
116 let mut delta = DeltaEdgeSlab::new();
117
118 delta.remove_edge(VertexId(0), VertexId(2));
120
121 let merged = delta.merged_view(&csr, VertexId(0));
122 assert_eq!(merged, vec![VertexId(1)]); }
124
125 #[test]
126 fn test_delta_slab_apply_to_csr() {
127 let csr = CsrEdges::from_adjacency(
128 vec![(0u32, vec![1u32]), (1u32, vec![2u32]), (2u32, vec![])],
129 &[
130 AbsCoord::new(0, 0),
131 AbsCoord::new(0, 1),
132 AbsCoord::new(1, 0),
133 ],
134 );
135
136 let mut delta = DeltaEdgeSlab::new();
137 delta.add_edge(VertexId(0), VertexId(2));
138 delta.remove_edge(VertexId(1), VertexId(2));
139 delta.add_edge(VertexId(2), VertexId(0));
140
141 let coords = vec![
143 AbsCoord::new(0, 0),
144 AbsCoord::new(0, 1),
145 AbsCoord::new(1, 0),
146 ];
147 let vertex_ids = vec![0u32, 1u32, 2u32];
148 let new_csr = delta.apply_to_csr(&csr, &coords, &vertex_ids);
149
150 assert_eq!(new_csr.out_edges(VertexId(0)), &[VertexId(1), VertexId(2)]);
151 assert_eq!(new_csr.out_edges(VertexId(1)), &[]);
152 assert_eq!(new_csr.out_edges(VertexId(2)), &[VertexId(0)]);
153 }
154
155 #[test]
156 fn test_mutable_edges_auto_rebuild() {
157 let mut edges = CsrMutableEdges::with_coords(vec![
158 AbsCoord::new(0, 0),
159 AbsCoord::new(0, 1),
160 AbsCoord::new(1, 0),
161 ]);
162
163 edges.add_edge(VertexId(0), VertexId(1));
165 edges.add_edge(VertexId(1), VertexId(2));
166
167 for _ in 0..500 {
169 edges.add_edge(VertexId(2), VertexId(0));
170 edges.remove_edge(VertexId(2), VertexId(0));
171 }
172
173 assert!(edges.delta_size() < 50);
175
176 assert_eq!(edges.out_edges(VertexId(0)), vec![VertexId(1)]);
178 assert_eq!(edges.out_edges(VertexId(1)), vec![VertexId(2)]);
179 }
180
181 #[test]
182 fn test_mutable_edges_with_offset_vertex_ids() {
183 use crate::engine::vertex_store::FIRST_NORMAL_VERTEX;
184
185 let mut edges = CsrMutableEdges::new();
186
187 let base_id = FIRST_NORMAL_VERTEX;
189 edges.add_vertex(AbsCoord::new(0, 0), base_id);
190 edges.add_vertex(AbsCoord::new(0, 1), base_id + 1);
191 edges.add_vertex(AbsCoord::new(1, 0), base_id + 2);
192
193 edges.add_edge(VertexId(base_id), VertexId(base_id + 1));
195 edges.add_edge(VertexId(base_id + 1), VertexId(base_id + 2));
196 edges.add_edge(VertexId(base_id + 2), VertexId(base_id));
197
198 assert_eq!(
200 edges.out_edges(VertexId(base_id)),
201 vec![VertexId(base_id + 1)]
202 );
203 assert_eq!(
204 edges.out_edges(VertexId(base_id + 1)),
205 vec![VertexId(base_id + 2)]
206 );
207 assert_eq!(
208 edges.out_edges(VertexId(base_id + 2)),
209 vec![VertexId(base_id)]
210 );
211
212 edges.rebuild();
214 assert_eq!(
215 edges.out_edges(VertexId(base_id)),
216 vec![VertexId(base_id + 1)]
217 );
218 assert_eq!(
219 edges.out_edges(VertexId(base_id + 1)),
220 vec![VertexId(base_id + 2)]
221 );
222 assert_eq!(
223 edges.out_edges(VertexId(base_id + 2)),
224 vec![VertexId(base_id)]
225 );
226 }
227
228 #[test]
229 fn test_csr_coord_update() {
230 let mut edges = CsrMutableEdges::new();
231
232 edges.add_vertex(AbsCoord::new(1, 1), 1024);
233 edges.add_vertex(AbsCoord::new(2, 2), 1025);
234 edges.add_edge(VertexId(1024), VertexId(1025));
235
236 edges.update_coord(VertexId(1024), AbsCoord::new(5, 5));
238
239 edges.rebuild();
241 let out = edges.out_edges(VertexId(1024));
242 assert_eq!(out, vec![VertexId(1025)]);
243 }
244
245 #[test]
246 fn update_coord_uses_vertex_position_index() {
247 let mut edges = CsrMutableEdges::new();
248 let items: Vec<_> = (0..20_000u32)
249 .map(|id| (AbsCoord::new(id, id % 17), id))
250 .collect();
251 edges.add_vertices_batch(&items);
252
253 let started = std::time::Instant::now();
254 for id in 15_000..20_000u32 {
255 edges.update_coord(VertexId(id), AbsCoord::new(id + 1, (id + 2) % 100));
256 }
257 let elapsed = started.elapsed();
258
259 if !cfg!(debug_assertions) {
260 assert!(
261 elapsed < std::time::Duration::from_millis(50),
262 "update_coord took {elapsed:?}"
263 );
264 }
265
266 for id in 15_000..20_000u32 {
267 let pos = edges.vertex_pos[&id];
268 assert_eq!(edges.vertex_ids[pos], id);
269 assert_eq!(edges.coords[pos], AbsCoord::new(id + 1, (id + 2) % 100));
270 }
271 }
272
273 #[test]
274 fn test_last_op_wins_add_then_remove() {
275 let csr = CsrEdges::from_adjacency(vec![(0u32, vec![])], &[AbsCoord::new(0, 0)]);
276 let mut delta = DeltaEdgeSlab::new();
277 delta.add_edge(VertexId(0), VertexId(1));
278 delta.remove_edge(VertexId(0), VertexId(1));
279 let merged = delta.merged_view(&csr, VertexId(0));
280 assert_eq!(merged, vec![]);
281 }
282
283 #[test]
284 fn test_last_op_wins_remove_then_add() {
285 let csr = CsrEdges::from_adjacency(vec![(0u32, vec![])], &[AbsCoord::new(0, 0)]);
286 let mut delta = DeltaEdgeSlab::new();
287 delta.remove_edge(VertexId(0), VertexId(1));
288 delta.add_edge(VertexId(0), VertexId(1));
289 let merged = delta.merged_view(&csr, VertexId(0));
290 assert_eq!(merged, vec![VertexId(1)]);
291 }
292
293 #[test]
294 fn test_dedup_additions_and_sorted() {
295 let csr = CsrEdges::from_adjacency(
296 vec![(0u32, vec![2u32])],
297 &[
298 AbsCoord::new(0, 0),
299 AbsCoord::new(0, 1),
300 AbsCoord::new(0, 2),
301 ],
302 );
303 let mut delta = DeltaEdgeSlab::new();
304 delta.add_edge(VertexId(0), VertexId(1));
306 delta.add_edge(VertexId(0), VertexId(3));
307 delta.add_edge(VertexId(0), VertexId(1)); let merged = delta.merged_view(&csr, VertexId(0));
309 assert_eq!(merged, vec![VertexId(1), VertexId(2), VertexId(3)]);
311 }
312
313 #[test]
314 fn test_end_batch_rebuilds_on_coord_change_only() {
315 let mut edges =
316 CsrMutableEdges::with_coords(vec![AbsCoord::new(0, 0), AbsCoord::new(0, 1)]);
317 edges.begin_batch();
318 edges.update_coord(VertexId(0), AbsCoord::new(0, 2));
319 edges.end_batch();
321 assert_eq!(edges.out_edges(VertexId(0)), Vec::<VertexId>::new());
323 }
324}
325
326#[derive(Debug)]
331pub struct DeltaEdgeSlab {
332 additions: FxHashMap<VertexId, FxHashSet<VertexId>>,
334
335 removals: FxHashMap<VertexId, FxHashSet<VertexId>>,
337
338 op_count: usize,
340
341 coord_changed: bool,
343}
344
345impl DeltaEdgeSlab {
346 pub fn new() -> Self {
348 Self {
349 additions: FxHashMap::default(),
350 removals: FxHashMap::default(),
351 op_count: 0,
352 coord_changed: false,
353 }
354 }
355
356 pub fn add_edge(&mut self, from: VertexId, to: VertexId) {
358 if let Some(rem) = self.removals.get_mut(&from) {
360 rem.remove(&to);
361 }
362 self.additions.entry(from).or_default().insert(to);
364 self.op_count += 1;
365 }
366
367 pub fn remove_edge(&mut self, from: VertexId, to: VertexId) {
369 if let Some(adds) = self.additions.get_mut(&from) {
371 adds.remove(&to);
372 }
373 self.removals.entry(from).or_default().insert(to);
375 self.op_count += 1;
376 }
377
378 pub fn merged_view(&self, csr: &CsrEdges, v: VertexId) -> Vec<VertexId> {
380 let mut result: Vec<_> = csr.out_edges(v).to_vec();
382 if let Some(removes) = self.removals.get(&v) {
384 result.retain(|e| !removes.contains(e));
385 }
386 if let Some(adds) = self.additions.get(&v) {
388 result.extend(adds.iter().copied());
389 }
390 let mut seen: FxHashSet<VertexId> = FxHashSet::default();
392 result.retain(|e| seen.insert(*e));
393 result.sort_by_key(|e| e.0);
394 result
395 }
396
397 pub fn needs_rebuild(&self) -> bool {
399 self.op_count >= 1000 || self.coord_changed
400 }
401
402 pub fn mark_dirty(&mut self) {
404 self.coord_changed = true;
405 }
406
407 pub fn op_count(&self) -> usize {
409 self.op_count
410 }
411
412 pub fn clear(&mut self) {
414 self.additions.clear();
415 self.removals.clear();
416 self.op_count = 0;
417 self.coord_changed = false;
418 }
419
420 pub fn additions_iter(&self) -> impl Iterator<Item = (&VertexId, &FxHashSet<VertexId>)> {
424 self.additions.iter()
425 }
426
427 pub fn removals_for(&self, from: VertexId) -> impl Iterator<Item = VertexId> + '_ {
429 self.removals
430 .get(&from)
431 .into_iter()
432 .flat_map(|set| set.iter().copied())
433 }
434
435 pub fn apply_to_csr(
437 &self,
438 base: &CsrEdges,
439 coords: &[AbsCoord],
440 vertex_ids: &[u32],
441 ) -> CsrEdges {
442 let mut adjacency = Vec::with_capacity(vertex_ids.len());
443
444 for &vid in vertex_ids {
446 let v = VertexId(vid);
447 let merged = self.merged_view(base, v);
448
449 let targets: Vec<u32> = merged.into_iter().map(|id| id.0).collect();
451
452 adjacency.push((vid, targets));
453 }
454
455 CsrEdges::from_adjacency(adjacency, coords)
456 }
457}
458
459impl Default for DeltaEdgeSlab {
460 fn default() -> Self {
461 Self::new()
462 }
463}
464
465#[derive(Debug)]
470pub struct CsrMutableEdges {
471 base: CsrEdges,
473
474 delta: DeltaEdgeSlab,
476
477 coords: Vec<AbsCoord>,
479
480 vertex_ids: Vec<u32>,
482
483 vertex_pos: FxHashMap<u32, usize>,
485
486 batch_depth: usize,
488}
489
490impl CsrMutableEdges {
491 pub fn new() -> Self {
493 Self {
494 base: CsrEdges::empty(),
495 delta: DeltaEdgeSlab::new(),
496 coords: Vec::new(),
497 vertex_ids: Vec::new(),
498 vertex_pos: FxHashMap::default(),
499 batch_depth: 0,
500 }
501 }
502
503 pub fn with_coords(coords: Vec<AbsCoord>) -> Self {
505 let num_vertices = coords.len();
506 let vertex_ids: Vec<u32> = (0..num_vertices as u32).collect();
507 let adjacency: Vec<_> = vertex_ids.iter().map(|&id| (id, Vec::new())).collect();
508 let vertex_pos = vertex_ids
509 .iter()
510 .enumerate()
511 .map(|(idx, &id)| (id, idx))
512 .collect();
513
514 Self {
515 base: CsrEdges::from_adjacency(adjacency, &coords),
516 delta: DeltaEdgeSlab::new(),
517 coords,
518 vertex_ids,
519 vertex_pos,
520 batch_depth: 0,
521 }
522 }
523
524 pub fn add_edge(&mut self, from: VertexId, to: VertexId) {
526 self.delta.add_edge(from, to);
527 self.maybe_rebuild();
528 }
529
530 pub fn remove_edge(&mut self, from: VertexId, to: VertexId) {
532 self.delta.remove_edge(from, to);
533 self.maybe_rebuild();
534 }
535
536 pub fn out_edges(&self, v: VertexId) -> Vec<VertexId> {
538 if self.delta.op_count() == 0 {
539 self.base.out_edges(v).to_vec()
540 } else {
541 self.delta.merged_view(&self.base, v)
542 }
543 }
544
545 #[inline]
549 pub fn out_edges_ref(&self, v: VertexId) -> Option<&[VertexId]> {
550 if self.delta.op_count() == 0 {
551 Some(self.base.out_edges(v))
552 } else {
553 None
554 }
555 }
556
557 pub fn in_edges(&self, v: VertexId) -> &[VertexId] {
560 self.base.in_edges(v)
561 }
562
563 #[inline]
567 pub fn in_edges_ref(&self, v: VertexId) -> Option<&[VertexId]> {
568 if self.delta.op_count() == 0 {
569 Some(self.base.in_edges(v))
570 } else {
571 None
572 }
573 }
574
575 pub fn delta_size(&self) -> usize {
577 self.delta.op_count()
578 }
579
580 pub fn num_edges_exact(&self) -> usize {
587 if self.delta.op_count() == 0 {
588 return self.base.num_edges();
589 }
590
591 self.vertex_ids
592 .iter()
593 .map(|&id| self.out_edges(VertexId(id)).len())
594 .sum()
595 }
596
597 pub fn rebuild(&mut self) {
599 if self.delta.op_count() > 0 || self.delta.needs_rebuild() {
600 self.base = self
601 .delta
602 .apply_to_csr(&self.base, &self.coords, &self.vertex_ids);
603 self.delta.clear();
604 }
605 }
606
607 fn maybe_rebuild(&mut self) {
609 if self.batch_depth == 0 && self.delta.needs_rebuild() {
610 self.rebuild();
611 }
612 }
613
614 pub fn begin_batch(&mut self) {
616 self.batch_depth = self.batch_depth.saturating_add(1);
617 }
618
619 pub fn end_batch(&mut self) {
621 self.batch_depth = self.batch_depth.saturating_sub(1);
622 if self.batch_depth == 0 && (self.delta.op_count() > 0 || self.delta.needs_rebuild()) {
623 self.rebuild();
624 }
625 }
626
627 pub fn add_vertex(&mut self, coord: AbsCoord, vertex_id: u32) -> usize {
629 let idx = self.coords.len();
630 self.coords.push(coord);
631 self.vertex_ids.push(vertex_id);
632 self.vertex_pos.insert(vertex_id, idx);
633
634 self.rebuild();
637
638 idx
639 }
640
641 pub fn add_vertices_batch(&mut self, items: &[(AbsCoord, u32)]) {
643 if items.is_empty() {
644 return;
645 }
646 let start_len = self.coords.len();
647 self.coords.reserve(items.len());
648 self.vertex_ids.reserve(items.len());
649 for (coord, vid) in items {
650 let idx = self.coords.len();
651 self.coords.push(*coord);
652 self.vertex_ids.push(*vid);
653 self.vertex_pos.insert(*vid, idx);
654 }
655 self.rebuild();
657 debug_assert_eq!(self.coords.len(), start_len + items.len());
658 }
659
660 pub fn update_coord(&mut self, vertex_id: VertexId, new_coord: AbsCoord) {
663 if let Some(&pos) = self.vertex_pos.get(&vertex_id.0) {
664 debug_assert_eq!(
665 self.vertex_ids[pos], vertex_id.0,
666 "vertex_pos out of sync with vertex_ids at position {pos}"
667 );
668 self.coords[pos] = new_coord;
669 self.delta.mark_dirty();
671 }
672 }
673
674 pub fn adjacency_with_carried_forward_edges(
689 &self,
690 mut adjacency: Vec<(u32, Vec<u32>)>,
691 ) -> Vec<(u32, Vec<u32>)> {
692 let covered: FxHashSet<u32> = adjacency.iter().map(|(vid, _)| *vid).collect();
693 for &vid in &self.vertex_ids {
697 if covered.contains(&vid) {
698 continue;
699 }
700 let v = VertexId(vid);
701 let merged = if self.delta.op_count() == 0 {
703 self.base.out_edges(v).to_vec()
704 } else {
705 self.delta.merged_view(&self.base, v)
706 };
707 if !merged.is_empty() {
708 adjacency.push((vid, merged.into_iter().map(|v| v.0).collect()));
709 }
710 }
711 for (&from, adds) in self.delta.additions_iter() {
716 if covered.contains(&from.0) {
717 continue;
718 }
719 if adjacency.iter().any(|(v, _)| *v == from.0) {
720 continue;
721 }
722 let removals: FxHashSet<u32> = self.delta.removals_for(from).map(|v| v.0).collect();
723 let mut base_set: FxHashSet<u32> =
724 self.base.out_edges(from).iter().map(|v| v.0).collect();
725 for r in &removals {
726 base_set.remove(r);
727 }
728 for &add in adds {
729 base_set.insert(add.0);
730 }
731 if !base_set.is_empty() {
732 let mut targets: Vec<u32> = base_set.into_iter().collect();
733 targets.sort_unstable();
734 adjacency.push((from.0, targets));
735 }
736 }
737 adjacency
738 }
739
740 pub fn build_from_adjacency(
748 &mut self,
749 adjacency: Vec<(u32, Vec<u32>)>,
750 coords: Vec<AbsCoord>,
751 vertex_ids: Vec<u32>,
752 ) {
753 self.base = CsrEdges::from_adjacency(adjacency, &coords);
754 self.coords = coords;
755 self.vertex_ids = vertex_ids;
756 self.vertex_pos = self
757 .vertex_ids
758 .iter()
759 .enumerate()
760 .map(|(idx, &id)| (id, idx))
761 .collect();
762 self.delta.clear();
763 }
764}
765
766impl Default for CsrMutableEdges {
767 fn default() -> Self {
768 Self::new()
769 }
770}