1use ahash::AHashMap;
2use smallvec::SmallVec;
3
4use crate::adjacency_list::AdjacencyList;
5use crate::set_family::{SetFamily, SetFamilyBuilder};
6use crate::types::{RowId, RowSet};
7
8type Key = SmallVec<[usize; 16]>;
9
10pub trait Graph {
12 fn node_count(&self) -> usize;
13 fn degree(&self, node: usize) -> usize;
14 fn contains(&self, node: usize, neighbor: usize) -> bool;
15 fn neighbors(&self, node: usize) -> impl Iterator<Item = usize> + '_;
16}
17
18pub trait AdjacencyStore: Graph + Sized {
20 type Builder: AdjacencyBuilder<Output = Self>;
21}
22
23pub trait AdjacencyBuilder {
29 type Output;
30
31 fn new(node_count: usize) -> Self;
32 fn add_undirected_edge(&mut self, a: usize, b: usize);
33 fn finish(self) -> Self::Output;
34}
35
36#[derive(Clone, Debug)]
37pub struct AdjacencyListBuilder {
38 adjacency: Vec<Vec<usize>>,
39}
40
41impl AdjacencyBuilder for AdjacencyListBuilder {
42 type Output = AdjacencyList;
43
44 #[inline]
45 fn new(node_count: usize) -> Self {
46 Self {
47 adjacency: vec![Vec::new(); node_count],
48 }
49 }
50
51 #[inline]
52 fn add_undirected_edge(&mut self, a: usize, b: usize) {
53 self.adjacency[a].push(b);
54 self.adjacency[b].push(a);
55 }
56
57 #[inline]
58 fn finish(mut self) -> Self::Output {
59 for neigh in &mut self.adjacency {
60 neigh.sort_unstable();
61 neigh.dedup();
62 }
63 AdjacencyList::from_sorted_adjacency_lists(self.adjacency)
64 }
65}
66
67impl Graph for AdjacencyList {
68 #[inline]
69 fn node_count(&self) -> usize {
70 self.num_vertices()
71 }
72
73 #[inline]
74 fn degree(&self, node: usize) -> usize {
75 self.adjacency_lists()[node].len()
76 }
77
78 #[inline]
79 fn contains(&self, node: usize, neighbor: usize) -> bool {
80 self.adjacency_lists()[node]
81 .binary_search(&neighbor)
82 .is_ok()
83 }
84
85 #[inline]
86 fn neighbors(&self, node: usize) -> impl Iterator<Item = usize> + '_ {
87 self.adjacency_lists()[node].iter().copied()
88 }
89}
90
91impl AdjacencyStore for AdjacencyList {
92 type Builder = AdjacencyListBuilder;
93}
94
95impl AdjacencyBuilder for SetFamilyBuilder {
96 type Output = SetFamily;
97
98 #[inline]
99 fn new(node_count: usize) -> Self {
100 SetFamily::builder(node_count, node_count)
101 }
102
103 #[inline]
104 fn add_undirected_edge(&mut self, a: usize, b: usize) {
105 self.insert_into_set(a, RowId::new(b));
106 self.insert_into_set(b, RowId::new(a));
107 }
108
109 #[inline]
110 fn finish(self) -> Self::Output {
111 self.build()
112 }
113}
114
115impl Graph for SetFamily {
116 #[inline]
117 fn node_count(&self) -> usize {
118 self.family_size()
119 }
120
121 #[inline]
122 fn degree(&self, node: usize) -> usize {
123 self.sets()[node].cardinality()
124 }
125
126 #[inline]
127 fn contains(&self, node: usize, neighbor: usize) -> bool {
128 self.sets()[node].contains(neighbor)
129 }
130
131 #[inline]
132 fn neighbors(&self, node: usize) -> impl Iterator<Item = usize> + '_ {
133 self.sets()[node].iter().raw()
134 }
135}
136
137impl AdjacencyStore for SetFamily {
138 type Builder = SetFamilyBuilder;
139}
140
141#[derive(Clone, Debug)]
142struct SparseRowMembershipIndex {
143 row_offsets: Vec<usize>,
144 members: Vec<usize>,
145}
146
147impl SparseRowMembershipIndex {
148 fn new<R: AsRef<[usize]>>(rows_by_node: &[R], row_capacity: usize) -> Self {
149 let mut degrees: Vec<usize> = vec![0; row_capacity];
150 for rows in rows_by_node {
151 for &row in rows.as_ref() {
152 if row < row_capacity {
153 degrees[row] = degrees[row].saturating_add(1);
154 }
155 }
156 }
157
158 let mut row_offsets: Vec<usize> = vec![0; row_capacity.saturating_add(1)];
159 for row in 0..row_capacity {
160 row_offsets[row + 1] = row_offsets[row].saturating_add(degrees[row]);
161 }
162
163 let mut members: Vec<usize> = vec![0; row_offsets[row_capacity]];
164 let mut cursor = row_offsets.clone();
165 for (node, rows) in rows_by_node.iter().enumerate() {
166 for &row in rows.as_ref() {
167 if row >= row_capacity {
168 continue;
169 }
170 let pos = cursor[row];
171 if pos < members.len() {
172 members[pos] = node;
173 cursor[row] = pos + 1;
174 }
175 }
176 }
177
178 Self {
179 row_offsets,
180 members,
181 }
182 }
183
184 #[inline]
185 fn members_of_row(&self, row: usize) -> &[usize] {
186 let Some((&start, &end)) = self
187 .row_offsets
188 .get(row)
189 .zip(self.row_offsets.get(row.saturating_add(1)))
190 else {
191 return &[];
192 };
193 self.members.get(start..end).unwrap_or(&[])
194 }
195
196 #[inline]
197 fn row_contains_node(&self, row: usize, node: usize) -> bool {
198 self.members_of_row(row).binary_search(&node).is_ok()
199 }
200}
201
202#[inline]
203fn intersect_sorted_rows(a: &[usize], b: &[usize], out: &mut Vec<usize>) {
204 out.clear();
205 let mut i = 0usize;
206 let mut j = 0usize;
207 while i < a.len() && j < b.len() {
208 match a[i].cmp(&b[j]) {
209 std::cmp::Ordering::Less => i += 1,
210 std::cmp::Ordering::Greater => j += 1,
211 std::cmp::Ordering::Equal => {
212 out.push(a[i]);
213 i += 1;
214 j += 1;
215 }
216 }
217 }
218}
219
220fn has_third_node_containing_face(
221 membership: &SparseRowMembershipIndex,
222 face_rows: &[usize],
223 node_a: usize,
224 node_b: usize,
225 non_excluded_nodes: usize,
226) -> bool {
227 if face_rows.is_empty() {
228 return non_excluded_nodes > 2;
230 }
231
232 let mut base_row = face_rows[0];
233 let mut base_len = membership.members_of_row(base_row).len();
234 for &row in face_rows.iter().skip(1) {
235 let len = membership.members_of_row(row).len();
236 if len < base_len {
237 base_row = row;
238 base_len = len;
239 if base_len <= 2 {
240 break;
241 }
242 }
243 }
244
245 for &candidate in membership.members_of_row(base_row) {
246 if candidate == node_a || candidate == node_b {
247 continue;
248 }
249 let mut ok = true;
250 for &row in face_rows {
251 if row == base_row {
252 continue;
253 }
254 if !membership.row_contains_node(row, candidate) {
255 ok = false;
256 break;
257 }
258 }
259 if ok {
260 return true;
261 }
262 }
263 false
264}
265
266#[derive(Clone, Copy, Debug, Default)]
268pub struct IncidenceAdjacencyOptions<'a> {
269 pub excluded_nodes: Option<&'a [bool]>,
273
274 pub active_rows: Option<&'a [bool]>,
278
279 pub candidate_edges: Option<&'a [(usize, usize)]>,
282
283 pub assume_nondegenerate: bool,
285}
286
287#[derive(Clone, Copy, Debug, Default)]
289pub struct RowsByNodeAdjacencyOptions<'a> {
290 pub excluded_nodes: Option<&'a [bool]>,
294
295 pub candidate_edges: Option<&'a [(usize, usize)]>,
298
299 pub assume_nondegenerate: bool,
301}
302
303pub fn input_adjacency_from_incidence_set_family(
304 incidence: &SetFamily,
305 redundant_nodes: &RowSet,
306 dominant_nodes: &RowSet,
307) -> SetFamily {
308 let family_size = incidence.family_size();
309 if redundant_nodes.len() != family_size {
310 panic!(
311 "redundant_nodes length mismatch (redundant_nodes={} family_size={})",
312 redundant_nodes.len(),
313 family_size
314 );
315 }
316 if dominant_nodes.len() != family_size {
317 panic!(
318 "dominant_nodes length mismatch (dominant_nodes={} family_size={})",
319 dominant_nodes.len(),
320 family_size
321 );
322 }
323
324 let row_capacity = incidence.set_capacity();
325 let mut rows_by_node: Vec<Vec<usize>> = Vec::with_capacity(family_size);
326
327 for idx in 0..family_size {
328 if redundant_nodes.contains(idx) || dominant_nodes.contains(idx) {
329 rows_by_node.push(Vec::new());
330 continue;
331 }
332 let set = incidence
333 .set(idx)
334 .unwrap_or_else(|| panic!("incidence SetFamily missing set for index {idx}"));
335 rows_by_node.push(set.iter().map(|id| id.as_index()).collect());
336 }
337
338 input_adjacency_from_rows_by_node_with::<SetFamilyBuilder>(
339 &rows_by_node,
340 row_capacity,
341 redundant_nodes,
342 dominant_nodes,
343 )
344}
345
346pub fn input_adjacency_from_rows_by_node_with<S: AdjacencyBuilder>(
347 rows_by_node: &[impl AsRef<[usize]>],
348 row_capacity: usize,
349 redundant_nodes: &RowSet,
350 dominant_nodes: &RowSet,
351) -> S::Output {
352 let family_size = rows_by_node.len();
353 if family_size == 0 {
354 return S::new(0).finish();
355 }
356 if redundant_nodes.len() != family_size {
357 panic!(
358 "redundant_nodes length mismatch (redundant_nodes={} family_size={})",
359 redundant_nodes.len(),
360 family_size
361 );
362 }
363 if dominant_nodes.len() != family_size {
364 panic!(
365 "dominant_nodes length mismatch (dominant_nodes={} family_size={})",
366 dominant_nodes.len(),
367 family_size
368 );
369 }
370
371 let mut excluded_nodes = vec![false; family_size];
372
373 let mut has_dominant = false;
374 let mut normal_first = usize::MAX;
375 let mut normal_second = usize::MAX;
376 let mut normal_count = 0usize;
377
378 for (idx, excluded_slot) in excluded_nodes.iter_mut().enumerate() {
379 let is_dominant = dominant_nodes.contains(idx);
380 has_dominant |= is_dominant;
381 let excluded = redundant_nodes.contains(idx) || is_dominant;
382 *excluded_slot = excluded;
383 if !excluded {
384 normal_count += 1;
385 if normal_count == 1 {
386 normal_first = idx;
387 } else if normal_count == 2 {
388 normal_second = idx;
389 }
390 }
391 }
392
393 let mut sink = S::new(family_size);
394 adjacency_from_rows_by_node_into(
395 &mut sink,
396 rows_by_node,
397 row_capacity,
398 3,
399 RowsByNodeAdjacencyOptions {
400 excluded_nodes: Some(&excluded_nodes),
401 candidate_edges: None,
402 assume_nondegenerate: false,
403 },
404 );
405
406 if normal_count == 2 {
407 sink.add_undirected_edge(normal_first, normal_second);
408 }
409
410 if has_dominant {
411 let mut nonredundant: Vec<usize> = Vec::new();
412 for idx in 0..family_size {
413 if !redundant_nodes.contains(idx) {
414 nonredundant.push(idx);
415 }
416 }
417
418 for dom in dominant_nodes.iter().map(|id| id.as_index()) {
419 if redundant_nodes.contains(dom) {
420 continue;
421 }
422 for &other in &nonredundant {
423 if other == dom {
424 continue;
425 }
426 sink.add_undirected_edge(dom, other);
427 }
428 }
429 }
430
431 sink.finish()
432}
433
434pub fn adjacency_from_incidence(
447 sets: &[Vec<usize>],
448 universe_size: usize,
449 adj_dim: usize,
450 options: IncidenceAdjacencyOptions<'_>,
451) -> AdjacencyList {
452 adjacency_from_incidence_with::<AdjacencyListBuilder>(sets, universe_size, adj_dim, options)
453}
454
455pub fn adjacency_from_incidence_with<S: AdjacencyBuilder>(
461 sets: &[Vec<usize>],
462 universe_size: usize,
463 adj_dim: usize,
464 options: IncidenceAdjacencyOptions<'_>,
465) -> S::Output {
466 let family_size = sets.len();
467 if family_size == 0 {
468 return S::new(0).finish();
469 }
470
471 let excluded = options.excluded_nodes;
472 if let Some(mask) = excluded {
473 assert!(
474 mask.len() == family_size,
475 "excluded_nodes length mismatch (excluded_nodes={} family_size={})",
476 mask.len(),
477 family_size
478 );
479 }
480 let is_excluded = |idx: usize| excluded.is_some_and(|m| m[idx]);
481
482 let active = options.active_rows;
483 if let Some(mask) = active {
484 assert!(
485 mask.len() == universe_size,
486 "active_rows length mismatch (active_rows={} universe_size={})",
487 mask.len(),
488 universe_size
489 );
490 }
491
492 let mut rows_by_node: Vec<Vec<usize>> = Vec::with_capacity(family_size);
493 for (idx, rows) in sets.iter().enumerate() {
494 if is_excluded(idx) {
495 rows_by_node.push(Vec::new());
496 continue;
497 }
498
499 let mut filtered: Vec<usize> = Vec::new();
500 if let Some(active) = active {
501 for &row in rows {
502 debug_assert!(
503 row < universe_size,
504 "row index out of range (node={idx} row={row} universe_size={universe_size})"
505 );
506 if active[row] {
507 filtered.push(row);
508 }
509 }
510 } else {
511 for &row in rows {
512 debug_assert!(
513 row < universe_size,
514 "row index out of range (node={idx} row={row} universe_size={universe_size})"
515 );
516 filtered.push(row);
517 }
518 }
519
520 debug_assert!(
521 filtered.windows(2).all(|w| w[0] < w[1]),
522 "adjacency_from_incidence expects per-node sets to be sorted + deduped"
523 );
524 rows_by_node.push(filtered);
525 }
526
527 adjacency_from_rows_by_node_with::<S>(
528 &rows_by_node,
529 universe_size,
530 adj_dim,
531 RowsByNodeAdjacencyOptions {
532 excluded_nodes: excluded,
533 candidate_edges: options.candidate_edges,
534 assume_nondegenerate: options.assume_nondegenerate,
535 },
536 )
537}
538
539pub fn adjacency_from_rows_by_node_with<S: AdjacencyBuilder>(
547 rows_by_node: &[impl AsRef<[usize]>],
548 row_capacity: usize,
549 adj_dim: usize,
550 options: RowsByNodeAdjacencyOptions<'_>,
551) -> S::Output {
552 let family_size = rows_by_node.len();
553 if family_size == 0 {
554 return S::new(0).finish();
555 }
556
557 let mut sink = S::new(family_size);
558 adjacency_from_rows_by_node_into(&mut sink, rows_by_node, row_capacity, adj_dim, options);
559 sink.finish()
560}
561
562fn adjacency_from_rows_by_node_into<S, Rows>(
563 sink: &mut S,
564 rows_by_node: &[Rows],
565 row_capacity: usize,
566 adj_dim: usize,
567 options: RowsByNodeAdjacencyOptions<'_>,
568) where
569 S: AdjacencyBuilder,
570 Rows: AsRef<[usize]>,
571{
572 let family_size = rows_by_node.len();
573 if family_size == 0 {
574 return;
575 }
576
577 let excluded = options.excluded_nodes;
578 if let Some(mask) = excluded {
579 assert!(
580 mask.len() == family_size,
581 "excluded_nodes length mismatch (excluded_nodes={} family_size={})",
582 mask.len(),
583 family_size
584 );
585 }
586 let is_excluded = |idx: usize| excluded.is_some_and(|m| m[idx]);
587
588 let required = adj_dim.saturating_sub(2);
589 let non_excluded = (0..family_size).filter(|&i| !is_excluded(i)).count();
590 if non_excluded < 2 {
591 return;
592 }
593
594 if options.candidate_edges.is_none() && required == 0 {
597 let non_excluded_indices: Vec<usize> =
598 (0..family_size).filter(|&i| !is_excluded(i)).collect();
599 if options.assume_nondegenerate || non_excluded_indices.len() == 2 {
600 for (pos, &i) in non_excluded_indices.iter().enumerate() {
601 for &j in non_excluded_indices.iter().skip(pos + 1) {
602 sink.add_undirected_edge(i, j);
603 }
604 }
605 }
606 return;
607 }
608
609 let mut simplicial_facet_dimension: Option<usize> = None;
616 if options.candidate_edges.is_none() && required >= 1 {
617 let facet_dimension = required + 1;
618 let simplicial_count = simplicial_ridge_hash_into_sink(
619 rows_by_node,
620 facet_dimension,
621 excluded,
622 options.assume_nondegenerate,
623 sink,
624 );
625 if simplicial_count == non_excluded {
626 return;
627 }
628 simplicial_facet_dimension = Some(facet_dimension);
629 }
630
631 let membership = SparseRowMembershipIndex::new(rows_by_node, row_capacity);
632 let mut face_rows: Vec<usize> = Vec::new();
633
634 if let Some(edges) = options.candidate_edges {
635 for &(i, j) in edges {
636 if i == j || i >= family_size || j >= family_size {
637 continue;
638 }
639 if is_excluded(i) || is_excluded(j) {
640 continue;
641 }
642 intersect_sorted_rows(
643 rows_by_node[i].as_ref(),
644 rows_by_node[j].as_ref(),
645 &mut face_rows,
646 );
647 if face_rows.len() < required {
648 continue;
649 }
650 if !options.assume_nondegenerate
651 && has_third_node_containing_face(&membership, &face_rows, i, j, non_excluded)
652 {
653 continue;
654 }
655 sink.add_undirected_edge(i, j);
656 }
657 return;
658 }
659
660 let mut counts: Vec<u32> = vec![0; family_size];
661 let mut touched: Vec<usize> = Vec::new();
662
663 for i in 0..family_size {
664 if is_excluded(i) {
665 continue;
666 }
667 if let Some(d) = simplicial_facet_dimension {
668 if rows_by_node[i].as_ref().len() == d {
669 continue;
670 }
671 }
672
673 touched.clear();
674 for &row in rows_by_node[i].as_ref() {
675 debug_assert!(row < row_capacity, "row index out of range in rows_by_node");
676 for &j in membership.members_of_row(row) {
677 let c = counts[j];
678 if c == 0 {
679 touched.push(j);
680 }
681 counts[j] = c + 1;
682 }
683 }
684
685 for &j in &touched {
686 if is_excluded(j) {
687 continue;
688 }
689 if let Some(d) = simplicial_facet_dimension {
690 if rows_by_node[j].as_ref().len() != d && j <= i {
691 continue;
692 }
693 } else if j <= i {
694 continue;
695 }
696 if (counts[j] as usize) < required {
697 continue;
698 }
699 if !options.assume_nondegenerate {
700 intersect_sorted_rows(
701 rows_by_node[i].as_ref(),
702 rows_by_node[j].as_ref(),
703 &mut face_rows,
704 );
705 if face_rows.len() < required {
706 continue;
707 }
708 if has_third_node_containing_face(&membership, &face_rows, i, j, non_excluded) {
709 continue;
710 }
711 }
712 sink.add_undirected_edge(i, j);
713 }
714
715 for &j in &touched {
716 counts[j] = 0;
717 }
718 }
719}
720
721fn simplicial_ridge_hash_into_sink<S: AdjacencyBuilder>(
722 facets: &[impl AsRef<[usize]>],
723 facet_dimension: usize,
724 excluded_nodes: Option<&[bool]>,
725 assume_nondegenerate: bool,
726 sink: &mut S,
727) -> usize {
728 if facet_dimension < 2 {
729 return 0;
730 }
731
732 let is_excluded = |idx: usize| excluded_nodes.is_some_and(|m| m[idx]);
733
734 let mut ridge_to_facets: AHashMap<Key, SmallVec<[usize; 2]>> = AHashMap::new();
735 let mut simplicial_count: usize = 0;
736 let ridge_len = facet_dimension - 1;
737 for (facet_idx, facet) in facets.iter().enumerate() {
738 if is_excluded(facet_idx) {
739 continue;
740 }
741 let facet = facet.as_ref();
742 if facet.len() != facet_dimension {
743 continue;
744 }
745 simplicial_count += 1;
746 for drop_pos in 0..facet_dimension {
747 let mut ridge = Key::with_capacity(ridge_len);
748 ridge.extend(facet[..drop_pos].iter().copied());
749 ridge.extend(facet[(drop_pos + 1)..].iter().copied());
750 ridge_to_facets.entry(ridge).or_default().push(facet_idx);
751 }
752 }
753
754 if assume_nondegenerate {
755 for incident in ridge_to_facets.values() {
756 for (pos, &a) in incident.iter().enumerate() {
757 for &b in incident.iter().skip(pos + 1) {
758 if a != b {
759 sink.add_undirected_edge(a, b);
760 }
761 }
762 }
763 }
764 } else {
765 for incident in ridge_to_facets.values() {
766 if let [a, b] = incident.as_slice() {
767 if a != b {
768 sink.add_undirected_edge(*a, *b);
769 }
770 }
771 }
772 }
773
774 simplicial_count
775}
776
777#[cfg(test)]
778mod tests {
779 use super::{IncidenceAdjacencyOptions, adjacency_from_incidence};
780
781 #[test]
782 fn square_facets_form_cycle() {
783 let facets = vec![vec![0, 1], vec![1, 3], vec![2, 3], vec![0, 2]];
786 let g = adjacency_from_incidence(&facets, 4, 3, IncidenceAdjacencyOptions::default());
787 let degrees: Vec<usize> = g.adjacency_lists().iter().map(|n| n.len()).collect();
788 assert_eq!(degrees, vec![2, 2, 2, 2]);
789 }
790}