1use crate::namespace::{CapabilityId, Namespace};
18use std::collections::{BTreeMap, BTreeSet};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct Matroid {
27 pub ground_set_size: usize,
29 pub bases: BTreeSet<BTreeSet<usize>>,
31 pub rank: usize,
33}
34
35impl Matroid {
36 pub fn uniform(k: usize, n: usize) -> Self {
46 let bases = k_subsets_btree(n, k);
47 Self {
48 ground_set_size: n,
49 bases,
50 rank: k,
51 }
52 }
53
54 pub fn from_bases(n: usize, bases: BTreeSet<BTreeSet<usize>>) -> Result<Self, String> {
66 if bases.is_empty() {
67 return Err("Matroid must have at least one basis".to_string());
68 }
69
70 let rank = bases.iter().next().unwrap().len();
71
72 if bases.iter().any(|b| b.len() != rank) {
74 return Err("All bases must have the same cardinality".to_string());
75 }
76
77 if bases.iter().any(|b| b.iter().any(|&e| e >= n)) {
79 return Err(format!("All elements must be < {n}"));
80 }
81
82 for b1 in &bases {
84 for b2 in &bases {
85 let diff: Vec<usize> = b1.difference(b2).copied().collect();
86 for &x in &diff {
87 let mut found_exchange = false;
88 for &y in b2.difference(b1) {
89 let mut candidate: BTreeSet<usize> = b1.clone();
90 candidate.remove(&x);
91 candidate.insert(y);
92 if bases.contains(&candidate) {
93 found_exchange = true;
94 break;
95 }
96 }
97 if !found_exchange {
98 return Err(format!(
99 "Basis exchange axiom fails: cannot exchange {x} from {:?} with element of {:?}",
100 b1, b2
101 ));
102 }
103 }
104 }
105 }
106
107 Ok(Self {
108 ground_set_size: n,
109 bases,
110 rank,
111 })
112 }
113
114 pub fn from_plucker(
126 k: usize,
127 n: usize,
128 coords: &[f64],
129 tolerance: f64,
130 ) -> Result<Self, String> {
131 let subsets: Vec<Vec<usize>> = k_subsets_vec(n, k);
132
133 if coords.len() != subsets.len() {
134 return Err(format!(
135 "Expected C({},{}) = {} Plücker coordinates, got {}",
136 n,
137 k,
138 subsets.len(),
139 coords.len()
140 ));
141 }
142
143 let mut bases = BTreeSet::new();
144 for (i, subset) in subsets.iter().enumerate() {
145 if coords[i].abs() > tolerance {
146 bases.insert(subset.iter().copied().collect::<BTreeSet<usize>>());
147 }
148 }
149
150 if bases.is_empty() {
151 return Err("All Plücker coordinates are zero".to_string());
152 }
153
154 Ok(Self {
155 ground_set_size: n,
156 bases,
157 rank: k,
158 })
159 }
160
161 #[must_use]
170 pub fn rank_of(&self, subset: &BTreeSet<usize>) -> usize {
171 self.bases
172 .iter()
173 .map(|b| b.intersection(subset).count())
174 .max()
175 .unwrap_or(0)
176 }
177
178 #[must_use]
189 pub fn circuits(&self) -> BTreeSet<BTreeSet<usize>> {
190 let mut circuits = BTreeSet::new();
191
192 for size in 2..=self.ground_set_size {
194 for subset in subsets_of_size(self.ground_set_size, size) {
195 let r = self.rank_of(&subset);
196 if r < size {
197 let is_minimal = subset.iter().all(|&e| {
199 let mut smaller: BTreeSet<usize> = subset.clone();
200 smaller.remove(&e);
201 self.rank_of(&smaller) == smaller.len()
202 });
203 if is_minimal {
204 circuits.insert(subset);
205 }
206 }
207 }
208
209 if size > self.rank + 1 {
211 break;
212 }
213 }
214
215 circuits
216 }
217
218 #[must_use]
227 pub fn dual(&self) -> Self {
228 let ground: BTreeSet<usize> = (0..self.ground_set_size).collect();
229 let dual_bases: BTreeSet<BTreeSet<usize>> = self
230 .bases
231 .iter()
232 .map(|b| ground.difference(b).copied().collect())
233 .collect();
234
235 Self {
236 ground_set_size: self.ground_set_size,
237 bases: dual_bases,
238 rank: self.ground_set_size - self.rank,
239 }
240 }
241
242 #[must_use]
251 pub fn direct_sum(&self, other: &Matroid) -> Self {
252 let offset = self.ground_set_size;
253 let mut bases = BTreeSet::new();
254
255 for b1 in &self.bases {
256 for b2 in &other.bases {
257 let mut combined: BTreeSet<usize> = b1.clone();
258 for &e in b2 {
259 combined.insert(e + offset);
260 }
261 bases.insert(combined);
262 }
263 }
264
265 Self {
266 ground_set_size: self.ground_set_size + other.ground_set_size,
267 bases,
268 rank: self.rank + other.rank,
269 }
270 }
271
272 #[must_use]
286 pub fn matroid_union(&self, other: &Matroid) -> Self {
287 assert_eq!(
288 self.ground_set_size, other.ground_set_size,
289 "Matroid union requires same ground set"
290 );
291
292 let n = self.ground_set_size;
293 let new_rank = (self.rank + other.rank).min(n);
294
295 let mut bases = BTreeSet::new();
297
298 for subset in subsets_of_size(n, new_rank) {
299 if self.is_union_independent(&subset, other) {
300 bases.insert(subset);
301 }
302 }
303
304 if bases.is_empty() {
306 for r in (1..new_rank).rev() {
307 for subset in subsets_of_size(n, r) {
308 if self.is_union_independent(&subset, other) {
309 bases.insert(subset);
310 }
311 }
312 if !bases.is_empty() {
313 return Self {
314 ground_set_size: n,
315 bases,
316 rank: r,
317 };
318 }
319 }
320 }
321
322 Self {
323 ground_set_size: n,
324 bases,
325 rank: new_rank,
326 }
327 }
328
329 fn is_union_independent(&self, set: &BTreeSet<usize>, other: &Matroid) -> bool {
331 let elements: Vec<usize> = set.iter().copied().collect();
334 let n = elements.len();
335
336 for mask in 0..(1u64 << n) {
338 let mut a: BTreeSet<usize> = BTreeSet::new();
339 let mut b: BTreeSet<usize> = BTreeSet::new();
340 for (i, &e) in elements.iter().enumerate() {
341 if mask & (1 << i) != 0 {
342 a.insert(e);
343 } else {
344 b.insert(e);
345 }
346 }
347 if self.rank_of(&a) >= a.len() && other.rank_of(&b) >= b.len() {
348 return true;
349 }
350 }
351 false
352 }
353
354 #[must_use]
362 pub fn is_loop(&self, e: usize) -> bool {
363 !self.bases.iter().any(|b| b.contains(&e))
364 }
365
366 #[must_use]
374 pub fn is_coloop(&self, e: usize) -> bool {
375 self.bases.iter().all(|b| b.contains(&e))
376 }
377
378 #[must_use]
387 pub fn delete(&self, e: usize) -> Self {
388 let bases: BTreeSet<BTreeSet<usize>> = self
389 .bases
390 .iter()
391 .filter(|b| !b.contains(&e))
392 .map(|b| b.iter().map(|&x| if x > e { x - 1 } else { x }).collect())
393 .collect();
394
395 let rank = if bases.is_empty() {
396 self.rank.saturating_sub(1)
397 } else {
398 bases.iter().next().unwrap().len()
399 };
400
401 Self {
402 ground_set_size: self.ground_set_size - 1,
403 bases,
404 rank,
405 }
406 }
407
408 #[must_use]
418 pub fn contract(&self, e: usize) -> Self {
419 let bases: BTreeSet<BTreeSet<usize>> = self
420 .bases
421 .iter()
422 .filter(|b| b.contains(&e))
423 .map(|b| {
424 b.iter()
425 .filter(|&&x| x != e)
426 .map(|&x| if x > e { x - 1 } else { x })
427 .collect()
428 })
429 .collect();
430
431 let rank = self.rank.saturating_sub(1);
432
433 Self {
434 ground_set_size: self.ground_set_size - 1,
435 bases,
436 rank,
437 }
438 }
439
440 #[must_use]
451 pub fn tutte_polynomial(&self) -> BTreeMap<(usize, usize), i64> {
452 if self.ground_set_size == 0 {
454 let mut result = BTreeMap::new();
455 if self.bases.contains(&BTreeSet::new()) {
456 result.insert((0, 0), 1);
457 }
458 return result;
459 }
460
461 let e = (0..self.ground_set_size).find(|&e| !self.is_loop(e) && !self.is_coloop(e));
463
464 match e {
465 Some(e) => {
466 let deleted = self.delete(e);
468 let contracted = self.contract(e);
469
470 let t_del = deleted.tutte_polynomial();
471 let t_con = contracted.tutte_polynomial();
472
473 let mut result = t_del;
474 for ((i, j), coeff) in t_con {
475 *result.entry((i, j)).or_insert(0) += coeff;
476 }
477 result
478 }
479 None => {
480 let loops = (0..self.ground_set_size)
482 .filter(|&e| self.is_loop(e))
483 .count();
484 let coloops = (0..self.ground_set_size)
485 .filter(|&e| self.is_coloop(e))
486 .count();
487
488 let mut result = BTreeMap::new();
490 result.insert((coloops, loops), 1);
491 result
492 }
493 }
494 }
495
496 pub fn intersection_cardinality(&self, other: &Matroid) -> usize {
508 assert_eq!(
509 self.ground_set_size, other.ground_set_size,
510 "Matroid intersection requires same ground set"
511 );
512
513 let n = self.ground_set_size;
514
515 let mut current: BTreeSet<usize> = BTreeSet::new();
517
518 while let Some(path) = self.find_augmenting_path(¤t, other, n) {
519 for &e in &path {
521 if current.contains(&e) {
522 current.remove(&e);
523 } else {
524 current.insert(e);
525 }
526 }
527 }
528
529 current.len()
530 }
531
532 fn find_augmenting_path(
536 &self,
537 current: &BTreeSet<usize>,
538 other: &Matroid,
539 n: usize,
540 ) -> Option<Vec<usize>> {
541 let outside: Vec<usize> = (0..n).filter(|e| !current.contains(e)).collect();
542
543 let x1: Vec<usize> = outside
545 .iter()
546 .copied()
547 .filter(|&e| {
548 let mut test = current.clone();
549 test.insert(e);
550 self.rank_of(&test) > current.len()
551 })
552 .collect();
553
554 let x2: BTreeSet<usize> = outside
556 .iter()
557 .copied()
558 .filter(|&e| {
559 let mut test = current.clone();
560 test.insert(e);
561 other.rank_of(&test) > current.len()
562 })
563 .collect();
564
565 let mut visited: BTreeSet<usize> = BTreeSet::new();
567 let mut parent: BTreeMap<usize, Option<usize>> = BTreeMap::new();
568 let mut queue: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
569
570 for &e in &x1 {
571 if !visited.contains(&e) {
572 visited.insert(e);
573 parent.insert(e, None);
574 queue.push_back(e);
575 }
576 }
577
578 while let Some(u) = queue.pop_front() {
579 if x2.contains(&u) {
580 let mut path = vec![u];
582 let mut cur = u;
583 while let Some(Some(p)) = parent.get(&cur) {
584 path.push(*p);
585 cur = *p;
586 }
587 path.reverse();
588 return Some(path);
589 }
590
591 if current.contains(&u) {
592 for &v in &outside {
594 if !visited.contains(&v) {
595 let mut test = current.clone();
596 test.remove(&u);
597 test.insert(v);
598 if self.rank_of(&test) >= current.len() {
599 visited.insert(v);
600 parent.insert(v, Some(u));
601 queue.push_back(v);
602 }
603 }
604 }
605 } else {
606 for &v in current {
608 if !visited.contains(&v) {
609 let mut test = current.clone();
610 test.remove(&v);
611 test.insert(u);
612 if other.rank_of(&test) >= current.len() {
613 visited.insert(v);
614 parent.insert(v, Some(u));
615 queue.push_back(v);
616 }
617 }
618 }
619 }
620 }
621
622 None
623 }
624
625 pub fn schubert_matroid(partition: &[usize], k: usize, n: usize) -> Result<Self, String> {
638 let m = n - k;
639 let mut lambda = vec![0usize; k];
641 for (i, &p) in partition.iter().enumerate() {
642 if i >= k {
643 break;
644 }
645 if p > m {
646 return Err(format!("Partition entry {} exceeds n-k={}", p, m));
647 }
648 lambda[i] = p;
649 }
650
651 let bounds: Vec<usize> = (0..k).map(|j| m + j - lambda[k - 1 - j]).collect();
653
654 let all_subsets = k_subsets_vec(n, k);
656 let mut bases = BTreeSet::new();
657
658 for subset in all_subsets {
659 let fits = subset.iter().enumerate().all(|(j, &i_j)| i_j <= bounds[j]);
660 if fits {
661 bases.insert(subset.into_iter().collect::<BTreeSet<usize>>());
662 }
663 }
664
665 if bases.is_empty() {
666 return Err("No valid bases for Schubert matroid".to_string());
667 }
668
669 Ok(Self {
670 ground_set_size: n,
671 bases,
672 rank: k,
673 })
674 }
675}
676
677impl Matroid {
679 #[must_use]
683 pub fn characteristic_polynomial(&self) -> Vec<i64> {
684 let r = self.rank;
685 let n = self.ground_set_size;
686 let mut coeffs = vec![0i64; r + 1];
687
688 for mask in 0..(1u64 << n) {
689 let subset: BTreeSet<usize> = (0..n).filter(|&i| mask & (1 << i) != 0).collect();
690 let size = subset.len();
691 let rank_a = self.rank_of(&subset);
692 let power = r - rank_a;
693 let sign = if size.is_multiple_of(2) { 1i64 } else { -1 };
694 coeffs[power] += sign;
695 }
696 coeffs
697 }
698
699 #[must_use]
701 pub fn characteristic_polynomial_eval(&self, q: i64) -> i64 {
702 let coeffs = self.characteristic_polynomial();
703 let mut result = 0i64;
704 let mut q_power = 1i64;
705 for &c in &coeffs {
706 result += c * q_power;
707 q_power *= q;
708 }
709 result
710 }
711
712 #[must_use]
714 pub fn beta_invariant(&self) -> i64 {
715 let coeffs = self.characteristic_polynomial();
716 let deriv_at_1: i64 = coeffs.iter().enumerate().map(|(i, &c)| i as i64 * c).sum();
717 let sign = if self.rank.is_multiple_of(2) {
718 1i64
719 } else {
720 -1
721 };
722 sign * deriv_at_1
723 }
724
725 #[must_use]
727 pub fn closure(&self, subset: &BTreeSet<usize>) -> BTreeSet<usize> {
728 let mut cl = subset.clone();
729 let rank_s = self.rank_of(&cl);
730 for e in 0..self.ground_set_size {
731 if !cl.contains(&e) {
732 let mut test = cl.clone();
733 test.insert(e);
734 if self.rank_of(&test) == rank_s {
735 cl.insert(e);
736 }
737 }
738 }
739 cl
740 }
741
742 #[must_use]
744 pub fn is_flat(&self, subset: &BTreeSet<usize>) -> bool {
745 self.closure(subset) == *subset
746 }
747
748 #[must_use]
750 pub fn flats(&self) -> Vec<Vec<BTreeSet<usize>>> {
751 let n = self.ground_set_size;
752 let r = self.rank;
753 let mut by_rank: Vec<Vec<BTreeSet<usize>>> = vec![Vec::new(); r + 1];
754
755 let mut seen: BTreeSet<BTreeSet<usize>> = BTreeSet::new();
757 for mask in 0..(1u64 << n) {
758 let subset: BTreeSet<usize> = (0..n).filter(|&i| mask & (1 << i) != 0).collect();
759 let flat = self.closure(&subset);
760 if seen.insert(flat.clone()) {
761 let rk = self.rank_of(&flat);
762 by_rank[rk].push(flat);
763 }
764 }
765 by_rank
766 }
767
768 #[must_use]
770 pub fn mobius_values(&self) -> BTreeMap<BTreeSet<usize>, i64> {
771 let flats_by_rank = self.flats();
772 let mut mu: BTreeMap<BTreeSet<usize>, i64> = BTreeMap::new();
773
774 let empty: BTreeSet<usize> = BTreeSet::new();
776 let cl_empty = self.closure(&empty);
777 mu.insert(cl_empty.clone(), 1);
778
779 for rank_flats in &flats_by_rank {
781 for flat in rank_flats {
782 if mu.contains_key(flat) {
783 continue;
784 }
785 let mut sum = 0i64;
786 for (g, &m) in &mu {
787 if g.is_subset(flat) && g != flat {
789 sum += m;
790 }
791 }
792 mu.insert(flat.clone(), -sum);
793 }
794 }
795 mu
796 }
797
798 #[must_use]
800 pub fn is_connected(&self) -> bool {
801 if self.ground_set_size <= 1 {
802 return true;
803 }
804 self.connected_components().len() == 1
805 }
806
807 #[must_use]
809 pub fn connected_components(&self) -> Vec<Matroid> {
810 let n = self.ground_set_size;
811 if n == 0 {
812 return vec![self.clone()];
813 }
814
815 let mut parent: Vec<usize> = (0..n).collect();
820
821 fn find(parent: &mut Vec<usize>, x: usize) -> usize {
822 if parent[x] != x {
823 parent[x] = find(parent, parent[x]);
824 }
825 parent[x]
826 }
827
828 fn union(parent: &mut Vec<usize>, x: usize, y: usize) {
829 let rx = find(parent, x);
830 let ry = find(parent, y);
831 if rx != ry {
832 parent[rx] = ry;
833 }
834 }
835
836 let circuits = self.circuits();
845 for circuit in &circuits {
846 let elems: Vec<usize> = circuit.iter().copied().collect();
847 for i in 1..elems.len() {
848 union(&mut parent, elems[0], elems[i]);
849 }
850 }
851
852 for e in 0..n {
854 for f in (e + 1)..n {
855 let mut pair = BTreeSet::new();
856 pair.insert(e);
857 pair.insert(f);
858 if self.rank_of(&pair) < 2 {
859 union(&mut parent, e, f);
860 }
861 }
862 }
863
864 let mut components: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
866 for e in 0..n {
867 let root = find(&mut parent, e);
868 components.entry(root).or_default().push(e);
869 }
870
871 if components.len() == 1 {
873 return vec![self.clone()];
874 }
875
876 components
878 .values()
879 .map(|elems| {
880 let elem_set: BTreeSet<usize> = elems.iter().copied().collect();
881 self.restrict(&elem_set)
882 })
883 .collect()
884 }
885
886 #[must_use]
888 pub fn f_vector(&self) -> Vec<u64> {
889 let n = self.ground_set_size;
890 let r = self.rank;
891 let mut f = vec![0u64; r + 1];
892
893 for mask in 0..(1u64 << n) {
894 let subset: BTreeSet<usize> = (0..n).filter(|&i| mask & (1 << i) != 0).collect();
895 let size = subset.len();
896 if size <= r && self.rank_of(&subset) == size {
897 f[size] += 1;
898 }
899 }
900 f
901 }
902
903 #[must_use]
907 pub fn h_vector(&self) -> Vec<i64> {
908 let f = self.f_vector();
909 let r = self.rank;
910 let mut h = vec![0i64; r + 1];
911
912 for (i, h_i) in h.iter_mut().enumerate() {
913 for (j, &f_j) in f.iter().enumerate().take(i + 1) {
914 let sign = if (i - j).is_multiple_of(2) { 1i64 } else { -1 };
915 let binom = binomial(r - j, i - j);
916 *h_i += sign * binom as i64 * f_j as i64;
917 }
918 }
919 h
920 }
921
922 #[must_use]
924 pub fn whitney_numbers_first_kind(&self) -> Vec<u64> {
925 self.characteristic_polynomial()
926 .iter()
927 .map(|c| c.unsigned_abs())
928 .collect()
929 }
930
931 #[must_use]
933 pub fn whitney_numbers_second_kind(&self) -> Vec<u64> {
934 self.flats().iter().map(|f| f.len() as u64).collect()
935 }
936
937 #[must_use]
939 pub fn restrict(&self, subset: &BTreeSet<usize>) -> Matroid {
940 let elems: Vec<usize> = subset.iter().copied().collect();
941 let new_n = elems.len();
942
943 let mut index_map: BTreeMap<usize, usize> = BTreeMap::new();
945 for (new_idx, &old_idx) in elems.iter().enumerate() {
946 index_map.insert(old_idx, new_idx);
947 }
948
949 let restricted_bases: BTreeSet<BTreeSet<usize>> = self
951 .bases
952 .iter()
953 .map(|b| {
954 b.intersection(subset)
955 .map(|e| index_map[e])
956 .collect::<BTreeSet<usize>>()
957 })
958 .collect();
959
960 let new_rank = self.rank_of(subset);
962
963 let bases: BTreeSet<BTreeSet<usize>> = restricted_bases
965 .into_iter()
966 .filter(|b| b.len() == new_rank)
967 .collect();
968
969 if bases.is_empty() && new_rank == 0 {
971 let mut b = BTreeSet::new();
972 b.insert(BTreeSet::new());
973 return Matroid {
974 ground_set_size: new_n,
975 bases: b,
976 rank: 0,
977 };
978 }
979
980 Matroid {
981 ground_set_size: new_n,
982 bases,
983 rank: new_rank,
984 }
985 }
986
987 #[must_use]
989 pub fn contraction_deletion(&self, e: usize) -> (Matroid, Matroid) {
990 (self.contract(e), self.delete(e))
991 }
992
993 #[must_use]
997 pub fn has_excluded_minor(&self, name: &str) -> bool {
998 match name {
999 "U24" => {
1000 let u24 = Matroid::uniform(2, 4);
1001 has_minor_check(self, &u24)
1002 }
1003 "F7" => {
1004 let f7 = fano_matroid_internal();
1005 has_minor_check(self, &f7)
1006 }
1007 "F7*" => {
1008 let f7 = fano_matroid_internal();
1009 has_minor_check(self, &f7.dual())
1010 }
1011 _ => false,
1012 }
1013 }
1014}
1015
1016fn has_minor_check(matroid: &Matroid, minor: &Matroid) -> bool {
1020 let n = matroid.ground_set_size;
1021 let m_n = minor.ground_set_size;
1022 let m_r = minor.rank;
1023
1024 if n < m_n || matroid.rank < m_r {
1025 return false;
1026 }
1027
1028 let contract_size = matroid.rank - m_r;
1031 let delete_size = n - m_n - contract_size;
1032
1033 if contract_size + delete_size + m_n != n {
1034 return false;
1035 }
1036
1037 for contract_set in subsets_of_size(n, contract_size) {
1039 let remaining: Vec<usize> = (0..n).filter(|e| !contract_set.contains(e)).collect();
1040
1041 if remaining.len() < m_n {
1042 continue;
1043 }
1044
1045 for del_indices in subsets_of_size_from(&remaining, delete_size) {
1047 let del_set: BTreeSet<usize> = del_indices.iter().copied().collect();
1048
1049 let mut result = matroid.clone();
1051
1052 let mut to_contract: Vec<usize> = contract_set.iter().copied().collect();
1054 to_contract.sort_unstable_by(|a, b| b.cmp(a));
1055 for &e in &to_contract {
1056 result = result.contract(e);
1057 }
1058
1059 let mut to_delete: Vec<usize> = Vec::new();
1061 for &d in &del_set {
1062 let adjusted = d - contract_set.iter().filter(|&&c| c < d).count();
1063 to_delete.push(adjusted);
1064 }
1065 to_delete.sort_unstable_by(|a, b| b.cmp(a));
1066
1067 for &e in &to_delete {
1068 if e < result.ground_set_size {
1069 result = result.delete(e);
1070 }
1071 }
1072
1073 if result.ground_set_size == minor.ground_set_size
1075 && result.rank == minor.rank
1076 && result.bases == minor.bases
1077 {
1078 return true;
1079 }
1080
1081 if m_n <= 6
1084 && result.ground_set_size == m_n
1085 && result.rank == m_r
1086 && is_matroid_isomorphic(&result, minor)
1087 {
1088 return true;
1089 }
1090 }
1091 }
1092 false
1093}
1094
1095fn is_matroid_isomorphic(a: &Matroid, b: &Matroid) -> bool {
1097 if a.ground_set_size != b.ground_set_size || a.rank != b.rank || a.bases.len() != b.bases.len()
1098 {
1099 return false;
1100 }
1101 let n = a.ground_set_size;
1102 if n > 8 {
1103 return a.bases == b.bases;
1104 }
1105
1106 let perms = permutations(n);
1108 for perm in &perms {
1109 let mapped_bases: BTreeSet<BTreeSet<usize>> = a
1110 .bases
1111 .iter()
1112 .map(|basis| basis.iter().map(|&e| perm[e]).collect())
1113 .collect();
1114 if mapped_bases == b.bases {
1115 return true;
1116 }
1117 }
1118 false
1119}
1120
1121fn permutations(n: usize) -> Vec<Vec<usize>> {
1122 if n == 0 {
1123 return vec![vec![]];
1124 }
1125 let mut result = Vec::new();
1126 let sub = permutations(n - 1);
1127 for p in sub {
1128 for i in 0..=p.len() {
1129 let mut new_p = p.clone();
1130 new_p.insert(i, n - 1);
1131 result.push(new_p);
1132 }
1133 }
1134 result
1135}
1136
1137fn subsets_of_size_from(elements: &[usize], k: usize) -> Vec<BTreeSet<usize>> {
1138 if k == 0 {
1139 return vec![BTreeSet::new()];
1140 }
1141 if elements.len() < k {
1142 return Vec::new();
1143 }
1144 let mut result = Vec::new();
1145 let mut current = Vec::with_capacity(k);
1146 gen_subsets_from(elements, k, 0, &mut current, &mut result);
1147 result
1148}
1149
1150fn gen_subsets_from(
1151 elements: &[usize],
1152 k: usize,
1153 start: usize,
1154 current: &mut Vec<usize>,
1155 result: &mut Vec<BTreeSet<usize>>,
1156) {
1157 if current.len() == k {
1158 result.push(current.iter().copied().collect());
1159 return;
1160 }
1161 let remaining = k - current.len();
1162 if start + remaining > elements.len() {
1163 return;
1164 }
1165 for i in start..=(elements.len() - remaining) {
1166 current.push(elements[i]);
1167 gen_subsets_from(elements, k, i + 1, current, result);
1168 current.pop();
1169 }
1170}
1171
1172fn fano_matroid_internal() -> Matroid {
1174 let n = 7;
1177 let k = 3;
1178 let lines: Vec<BTreeSet<usize>> = vec![
1179 [0, 1, 3].iter().copied().collect(),
1180 [1, 2, 4].iter().copied().collect(),
1181 [2, 3, 5].iter().copied().collect(),
1182 [3, 4, 6].iter().copied().collect(),
1183 [0, 4, 5].iter().copied().collect(),
1184 [1, 5, 6].iter().copied().collect(),
1185 [0, 2, 6].iter().copied().collect(),
1186 ];
1187
1188 let all_triples = k_subsets_btree(n, k);
1190 let bases: BTreeSet<BTreeSet<usize>> = all_triples
1191 .into_iter()
1192 .filter(|t| !lines.contains(t))
1193 .collect();
1194
1195 Matroid {
1196 ground_set_size: n,
1197 bases,
1198 rank: k,
1199 }
1200}
1201
1202fn binomial(n: usize, k: usize) -> u64 {
1203 if k > n {
1204 return 0;
1205 }
1206 if k == 0 || k == n {
1207 return 1;
1208 }
1209 let k = k.min(n - k);
1210 let mut result: u64 = 1;
1211 for i in 0..k {
1212 result = result * (n - i) as u64 / (i + 1) as u64;
1213 }
1214 result
1215}
1216
1217impl Namespace {
1219 #[must_use]
1231 pub fn capability_matroid(&self) -> Option<Matroid> {
1232 let n = self.capabilities.len();
1233 if n == 0 {
1234 return None;
1235 }
1236
1237 let (k, dim_n) = self.grassmannian;
1238 let gr_dim = k * (dim_n - k);
1239
1240 let mut max_size = 0;
1242 let mut candidates: Vec<BTreeSet<usize>> = Vec::new();
1243
1244 for mask in 1u64..(1u64 << n) {
1246 let subset: Vec<usize> = (0..n).filter(|&i| mask & (1 << i) != 0).collect();
1247 let total_codim: usize = subset
1248 .iter()
1249 .map(|&i| self.capabilities[i].codimension())
1250 .sum();
1251
1252 if total_codim <= gr_dim {
1253 let size = subset.len();
1254 if size > max_size {
1255 max_size = size;
1256 candidates.clear();
1257 }
1258 if size == max_size {
1259 candidates.push(subset.into_iter().collect());
1260 }
1261 }
1262 }
1263
1264 if candidates.is_empty() {
1265 return None;
1266 }
1267
1268 let bases: BTreeSet<BTreeSet<usize>> = candidates.into_iter().collect();
1269 Some(Matroid {
1270 ground_set_size: n,
1271 bases,
1272 rank: max_size,
1273 })
1274 }
1275
1276 #[must_use]
1287 pub fn redundant_capabilities(&self) -> Vec<CapabilityId> {
1288 let Some(matroid) = self.capability_matroid() else {
1289 return Vec::new();
1290 };
1291
1292 let mut redundant = Vec::new();
1293 for (i, cap) in self.capabilities.iter().enumerate() {
1294 if matroid.is_loop(i) {
1295 redundant.push(cap.id.clone());
1296 }
1297 }
1298 redundant
1299 }
1300
1301 #[must_use]
1311 pub fn max_shared_capabilities(&self, other: &Namespace) -> usize {
1312 let m1 = self.capability_matroid();
1313 let m2 = other.capability_matroid();
1314
1315 match (m1, m2) {
1316 (Some(m1), Some(m2)) if m1.ground_set_size == m2.ground_set_size => {
1317 m1.intersection_cardinality(&m2)
1318 }
1319 _ => 0,
1320 }
1321 }
1322}
1323
1324#[derive(Debug, Clone)]
1328pub struct ValuatedMatroid {
1329 pub matroid: Matroid,
1331 pub valuation: BTreeMap<BTreeSet<usize>, f64>,
1333}
1334
1335impl ValuatedMatroid {
1336 pub fn from_tropical_plucker(k: usize, n: usize, coords: &[f64]) -> Result<Self, String> {
1345 let subsets = k_subsets_vec(n, k);
1346
1347 if coords.len() != subsets.len() {
1348 return Err(format!(
1349 "Expected {} tropical Plücker coordinates, got {}",
1350 subsets.len(),
1351 coords.len()
1352 ));
1353 }
1354
1355 let mut bases = BTreeSet::new();
1356 let mut valuation = BTreeMap::new();
1357
1358 for (i, subset) in subsets.iter().enumerate() {
1359 if coords[i].is_finite() {
1360 let basis: BTreeSet<usize> = subset.iter().copied().collect();
1361 bases.insert(basis.clone());
1362 valuation.insert(basis, coords[i]);
1363 }
1364 }
1365
1366 if bases.is_empty() {
1367 return Err("No finite tropical Plücker coordinates".to_string());
1368 }
1369
1370 let rank = k;
1371 let matroid = Matroid {
1372 ground_set_size: n,
1373 bases,
1374 rank,
1375 };
1376
1377 Ok(Self { matroid, valuation })
1378 }
1379
1380 #[must_use]
1387 pub fn satisfies_tropical_plucker(&self) -> bool {
1388 if self.matroid.rank <= 1 || self.matroid.ground_set_size <= 3 {
1390 return true;
1391 }
1392
1393 let k = self.matroid.rank;
1395 let _n = self.matroid.ground_set_size;
1396
1397 if k < 2 {
1398 return true;
1399 }
1400
1401 for b1 in &self.matroid.bases {
1403 for b2 in &self.matroid.bases {
1404 let diff1: Vec<usize> = b1.difference(b2).copied().collect();
1405 let diff2: Vec<usize> = b2.difference(b1).copied().collect();
1406
1407 if diff1.len() != 2 || diff2.len() != 2 {
1408 continue;
1409 }
1410
1411 let v1 = self.valuation.get(b1).copied().unwrap_or(f64::INFINITY);
1413 let v2 = self.valuation.get(b2).copied().unwrap_or(f64::INFINITY);
1414
1415 let i = diff1[0];
1417 let _j = diff1[1];
1418 let a = diff2[0];
1419 let b_elem = diff2[1];
1420
1421 let mut exchange1 = b1.clone();
1423 exchange1.remove(&i);
1424 exchange1.insert(a);
1425
1426 let mut exchange2 = b1.clone();
1427 exchange2.remove(&i);
1428 exchange2.insert(b_elem);
1429
1430 let v_ex1 = self
1431 .valuation
1432 .get(&exchange1)
1433 .copied()
1434 .unwrap_or(f64::INFINITY);
1435 let v_ex2 = self
1436 .valuation
1437 .get(&exchange2)
1438 .copied()
1439 .unwrap_or(f64::INFINITY);
1440
1441 if v1.is_finite() && v2.is_finite() && v_ex1.is_infinite() && v_ex2.is_infinite() {
1443 return false;
1444 }
1445 }
1446 }
1447
1448 true
1449 }
1450
1451 #[cfg(feature = "tropical-schubert")]
1455 #[must_use]
1456 pub fn to_tropical_schubert(&self) -> crate::tropical_schubert::TropicalSchubertClass {
1457 let weights: Vec<i64> = self.valuation.values().map(|&v| v as i64).collect();
1459
1460 crate::tropical_schubert::TropicalSchubertClass::new(weights)
1461 }
1462}
1463
1464fn k_subsets_btree(n: usize, k: usize) -> BTreeSet<BTreeSet<usize>> {
1467 k_subsets_vec(n, k)
1468 .into_iter()
1469 .map(|s| s.into_iter().collect())
1470 .collect()
1471}
1472
1473fn k_subsets_vec(n: usize, k: usize) -> Vec<Vec<usize>> {
1474 let mut result = Vec::new();
1475 let mut current = Vec::with_capacity(k);
1476 gen_subsets(n, k, 0, &mut current, &mut result);
1477 result
1478}
1479
1480fn gen_subsets(
1481 n: usize,
1482 k: usize,
1483 start: usize,
1484 current: &mut Vec<usize>,
1485 result: &mut Vec<Vec<usize>>,
1486) {
1487 if current.len() == k {
1488 result.push(current.clone());
1489 return;
1490 }
1491 let remaining = k - current.len();
1492 if start + remaining > n {
1493 return;
1494 }
1495 for i in start..=(n - remaining) {
1496 current.push(i);
1497 gen_subsets(n, k, i + 1, current, result);
1498 current.pop();
1499 }
1500}
1501
1502fn subsets_of_size(n: usize, k: usize) -> Vec<BTreeSet<usize>> {
1503 k_subsets_vec(n, k)
1504 .into_iter()
1505 .map(|s| s.into_iter().collect())
1506 .collect()
1507}
1508
1509#[cfg(feature = "parallel")]
1511#[must_use]
1512pub fn circuits_batch(matroids: &[Matroid]) -> Vec<BTreeSet<BTreeSet<usize>>> {
1513 use rayon::prelude::*;
1514 matroids.par_iter().map(|m| m.circuits()).collect()
1515}
1516
1517#[cfg(feature = "parallel")]
1519#[must_use]
1520pub fn tutte_polynomial_batch(matroids: &[Matroid]) -> Vec<BTreeMap<(usize, usize), i64>> {
1521 use rayon::prelude::*;
1522 matroids.par_iter().map(|m| m.tutte_polynomial()).collect()
1523}
1524
1525#[cfg(feature = "parallel")]
1527#[must_use]
1528pub fn intersection_cardinality_batch(pairs: &[(Matroid, Matroid)]) -> Vec<usize> {
1529 use rayon::prelude::*;
1530 pairs
1531 .par_iter()
1532 .map(|(m1, m2)| m1.intersection_cardinality(m2))
1533 .collect()
1534}
1535
1536#[cfg(test)]
1537mod tests {
1538 use super::*;
1539 use crate::namespace::Capability;
1540 use crate::schubert::SchubertClass;
1541
1542 #[test]
1543 fn test_uniform_matroid() {
1544 let m = Matroid::uniform(2, 4);
1545 assert_eq!(m.rank, 2);
1546 assert_eq!(m.bases.len(), 6); assert_eq!(m.ground_set_size, 4);
1548 }
1549
1550 #[test]
1551 fn test_matroid_from_plucker() {
1552 let coords = vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; let m = Matroid::from_plucker(2, 4, &coords, 0.001).unwrap();
1555 assert_eq!(m.rank, 2);
1556 assert_eq!(m.bases.len(), 6);
1557 }
1558
1559 #[test]
1560 fn test_matroid_from_plucker_partial() {
1561 let coords = vec![1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
1563 let m = Matroid::from_plucker(2, 4, &coords, 0.001).unwrap();
1564 assert_eq!(m.rank, 2);
1565 assert_eq!(m.bases.len(), 4);
1566 }
1567
1568 #[test]
1569 fn test_schubert_matroid() {
1570 let m = Matroid::schubert_matroid(&[1], 2, 4).unwrap();
1574 assert_eq!(m.rank, 2);
1575 assert_eq!(m.bases.len(), 3);
1576 }
1577
1578 #[test]
1579 fn test_schubert_matroid_empty_partition() {
1580 let m = Matroid::schubert_matroid(&[], 2, 4).unwrap();
1582 assert_eq!(m.bases.len(), 6);
1583 }
1584
1585 #[test]
1586 fn test_matroid_circuits() {
1587 let m = Matroid::uniform(2, 4);
1589 let circuits = m.circuits();
1590 assert_eq!(circuits.len(), 4); }
1592
1593 #[test]
1594 fn test_matroid_dual() {
1595 let m = Matroid::uniform(2, 4);
1596 let dual = m.dual();
1597 assert_eq!(dual.rank, 2); assert_eq!(dual.bases.len(), 6); }
1600
1601 #[test]
1602 fn test_matroid_direct_sum() {
1603 let m1 = Matroid::uniform(1, 2);
1604 let m2 = Matroid::uniform(1, 2);
1605 let sum = m1.direct_sum(&m2);
1606 assert_eq!(sum.rank, 2);
1607 assert_eq!(sum.ground_set_size, 4);
1608 }
1609
1610 #[test]
1611 fn test_matroid_loop_coloop() {
1612 let m = Matroid::uniform(2, 3);
1614 for e in 0..3 {
1615 assert!(!m.is_loop(e));
1616 assert!(!m.is_coloop(e));
1617 }
1618 }
1619
1620 #[test]
1621 fn test_matroid_delete_contract() {
1622 let m = Matroid::uniform(2, 4);
1623
1624 let deleted = m.delete(0);
1625 assert_eq!(deleted.ground_set_size, 3);
1626 assert_eq!(deleted.rank, 2);
1627
1628 let contracted = m.contract(0);
1629 assert_eq!(contracted.ground_set_size, 3);
1630 assert_eq!(contracted.rank, 1);
1631 }
1632
1633 #[test]
1634 fn test_matroid_intersection_cardinality() {
1635 let m1 = Matroid::uniform(2, 4);
1636 let m2 = Matroid::uniform(2, 4);
1637 assert_eq!(m1.intersection_cardinality(&m2), 2);
1639 }
1640
1641 #[test]
1642 fn test_tutte_polynomial_uniform() {
1643 let m = Matroid::uniform(1, 2);
1644 let tutte = m.tutte_polynomial();
1645 assert_eq!(tutte.get(&(1, 0)).copied().unwrap_or(0), 1);
1647 assert_eq!(tutte.get(&(0, 1)).copied().unwrap_or(0), 1);
1648 }
1649
1650 #[test]
1651 fn test_namespace_capability_matroid() {
1652 let pos = SchubertClass::new(vec![], (2, 4)).unwrap();
1653 let mut ns = Namespace::new("test", pos);
1654
1655 let cap1 = Capability::new("c1", "Cap 1", vec![1], (2, 4)).unwrap();
1656 let cap2 = Capability::new("c2", "Cap 2", vec![1], (2, 4)).unwrap();
1657 ns.grant(cap1).unwrap();
1658 ns.grant(cap2).unwrap();
1659
1660 let matroid = ns.capability_matroid();
1661 assert!(matroid.is_some());
1662 let m = matroid.unwrap();
1663 assert_eq!(m.ground_set_size, 2);
1664 }
1665
1666 #[test]
1667 fn test_valuated_matroid_tropical_plucker() {
1668 let coords = vec![0.0, 1.0, 2.0, 1.0, 2.0, 3.0]; let vm = ValuatedMatroid::from_tropical_plucker(2, 4, &coords).unwrap();
1670 assert_eq!(vm.matroid.rank, 2);
1671 assert!(vm.satisfies_tropical_plucker());
1672 }
1673
1674 #[test]
1675 fn test_characteristic_polynomial_u24() {
1676 let m = Matroid::uniform(2, 4);
1677 let chi = m.characteristic_polynomial();
1678 assert_eq!(chi, vec![3, -4, 1]);
1680 }
1681
1682 #[test]
1683 fn test_characteristic_polynomial_eval_at_1() {
1684 let m = Matroid::uniform(2, 4);
1686 assert_eq!(m.characteristic_polynomial_eval(1), 0);
1687 }
1688
1689 #[test]
1690 fn test_beta_invariant() {
1691 let m = Matroid::uniform(2, 4);
1692 let beta = m.beta_invariant();
1693 assert_eq!(beta, -2);
1696 }
1697
1698 #[test]
1699 fn test_closure_idempotent() {
1700 let m = Matroid::uniform(2, 4);
1701 let s: BTreeSet<usize> = [0].iter().copied().collect();
1702 let cl = m.closure(&s);
1703 let cl2 = m.closure(&cl);
1704 assert_eq!(cl, cl2);
1705 }
1706
1707 #[test]
1708 fn test_flats_of_u24() {
1709 let m = Matroid::uniform(2, 4);
1710 let flats = m.flats();
1711 assert_eq!(flats[0].len(), 1);
1713 assert_eq!(flats[1].len(), 4);
1715 assert_eq!(flats[2].len(), 1);
1717 }
1718
1719 #[test]
1720 fn test_f_vector_uniform() {
1721 let m = Matroid::uniform(2, 4);
1722 let f = m.f_vector();
1723 assert_eq!(f, vec![1, 4, 6]);
1725 }
1726
1727 #[test]
1728 fn test_whitney_second_kind() {
1729 let m = Matroid::uniform(2, 4);
1730 let w = m.whitney_numbers_second_kind();
1731 assert_eq!(w, vec![1, 4, 1]);
1733 }
1734
1735 #[test]
1736 fn test_restrict() {
1737 let m = Matroid::uniform(2, 4);
1738 let s: BTreeSet<usize> = [0, 1, 2].iter().copied().collect();
1739 let r = m.restrict(&s);
1740 assert_eq!(r.ground_set_size, 3);
1741 assert_eq!(r.rank, 2);
1742 assert_eq!(r.bases.len(), 3);
1744 }
1745
1746 #[test]
1747 fn test_contraction_deletion_pair() {
1748 let m = Matroid::uniform(2, 4);
1749 let (contracted, deleted) = m.contraction_deletion(0);
1750 assert_eq!(contracted.ground_set_size, 3);
1751 assert_eq!(contracted.rank, 1);
1752 assert_eq!(deleted.ground_set_size, 3);
1753 assert_eq!(deleted.rank, 2);
1754 }
1755
1756 #[test]
1757 fn test_connected_uniform() {
1758 let m = Matroid::uniform(2, 4);
1759 assert!(m.is_connected());
1760 }
1761
1762 #[test]
1763 fn test_connected_components_direct_sum() {
1764 let m1 = Matroid::uniform(1, 2);
1765 let m2 = Matroid::uniform(1, 2);
1766 let sum = m1.direct_sum(&m2);
1767 let components = sum.connected_components();
1768 assert_eq!(components.len(), 2);
1769 }
1770
1771 #[test]
1772 fn test_has_excluded_minor_u24() {
1773 let m = Matroid::uniform(2, 5);
1775 assert!(m.has_excluded_minor("U24"));
1776 let m2 = Matroid::uniform(2, 3);
1778 assert!(!m2.has_excluded_minor("U24"));
1779 }
1780
1781 #[cfg(feature = "parallel")]
1782 #[test]
1783 fn test_circuits_batch() {
1784 let matroids = vec![Matroid::uniform(2, 4), Matroid::uniform(1, 3)];
1785 let results = super::circuits_batch(&matroids);
1786 assert_eq!(results[0].len(), 4); assert_eq!(results[1].len(), 3); }
1789
1790 #[cfg(feature = "parallel")]
1791 #[test]
1792 fn test_tutte_polynomial_batch() {
1793 let matroids = vec![Matroid::uniform(1, 2), Matroid::uniform(1, 3)];
1794 let results = super::tutte_polynomial_batch(&matroids);
1795 assert_eq!(results.len(), 2);
1796 assert_eq!(results[0].get(&(1, 0)).copied().unwrap_or(0), 1);
1798 assert_eq!(results[0].get(&(0, 1)).copied().unwrap_or(0), 1);
1799 }
1800
1801 #[cfg(feature = "parallel")]
1802 #[test]
1803 fn test_intersection_cardinality_batch() {
1804 let pairs = vec![
1805 (Matroid::uniform(2, 4), Matroid::uniform(2, 4)),
1806 (Matroid::uniform(1, 3), Matroid::uniform(1, 3)),
1807 ];
1808 let results = super::intersection_cardinality_batch(&pairs);
1809 assert_eq!(results, vec![2, 1]);
1810 }
1811}