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 Namespace {
679 #[must_use]
691 pub fn capability_matroid(&self) -> Option<Matroid> {
692 let n = self.capabilities.len();
693 if n == 0 {
694 return None;
695 }
696
697 let (k, dim_n) = self.grassmannian;
698 let gr_dim = k * (dim_n - k);
699
700 let mut max_size = 0;
702 let mut candidates: Vec<BTreeSet<usize>> = Vec::new();
703
704 for mask in 1u64..(1u64 << n) {
706 let subset: Vec<usize> = (0..n).filter(|&i| mask & (1 << i) != 0).collect();
707 let total_codim: usize = subset
708 .iter()
709 .map(|&i| self.capabilities[i].codimension())
710 .sum();
711
712 if total_codim <= gr_dim {
713 let size = subset.len();
714 if size > max_size {
715 max_size = size;
716 candidates.clear();
717 }
718 if size == max_size {
719 candidates.push(subset.into_iter().collect());
720 }
721 }
722 }
723
724 if candidates.is_empty() {
725 return None;
726 }
727
728 let bases: BTreeSet<BTreeSet<usize>> = candidates.into_iter().collect();
729 Some(Matroid {
730 ground_set_size: n,
731 bases,
732 rank: max_size,
733 })
734 }
735
736 #[must_use]
747 pub fn redundant_capabilities(&self) -> Vec<CapabilityId> {
748 let Some(matroid) = self.capability_matroid() else {
749 return Vec::new();
750 };
751
752 let mut redundant = Vec::new();
753 for (i, cap) in self.capabilities.iter().enumerate() {
754 if matroid.is_loop(i) {
755 redundant.push(cap.id.clone());
756 }
757 }
758 redundant
759 }
760
761 #[must_use]
771 pub fn max_shared_capabilities(&self, other: &Namespace) -> usize {
772 let m1 = self.capability_matroid();
773 let m2 = other.capability_matroid();
774
775 match (m1, m2) {
776 (Some(m1), Some(m2)) if m1.ground_set_size == m2.ground_set_size => {
777 m1.intersection_cardinality(&m2)
778 }
779 _ => 0,
780 }
781 }
782}
783
784#[derive(Debug, Clone)]
788pub struct ValuatedMatroid {
789 pub matroid: Matroid,
791 pub valuation: BTreeMap<BTreeSet<usize>, f64>,
793}
794
795impl ValuatedMatroid {
796 pub fn from_tropical_plucker(k: usize, n: usize, coords: &[f64]) -> Result<Self, String> {
805 let subsets = k_subsets_vec(n, k);
806
807 if coords.len() != subsets.len() {
808 return Err(format!(
809 "Expected {} tropical Plücker coordinates, got {}",
810 subsets.len(),
811 coords.len()
812 ));
813 }
814
815 let mut bases = BTreeSet::new();
816 let mut valuation = BTreeMap::new();
817
818 for (i, subset) in subsets.iter().enumerate() {
819 if coords[i].is_finite() {
820 let basis: BTreeSet<usize> = subset.iter().copied().collect();
821 bases.insert(basis.clone());
822 valuation.insert(basis, coords[i]);
823 }
824 }
825
826 if bases.is_empty() {
827 return Err("No finite tropical Plücker coordinates".to_string());
828 }
829
830 let rank = k;
831 let matroid = Matroid {
832 ground_set_size: n,
833 bases,
834 rank,
835 };
836
837 Ok(Self { matroid, valuation })
838 }
839
840 #[must_use]
847 pub fn satisfies_tropical_plucker(&self) -> bool {
848 if self.matroid.rank <= 1 || self.matroid.ground_set_size <= 3 {
850 return true;
851 }
852
853 let k = self.matroid.rank;
855 let _n = self.matroid.ground_set_size;
856
857 if k < 2 {
858 return true;
859 }
860
861 for b1 in &self.matroid.bases {
863 for b2 in &self.matroid.bases {
864 let diff1: Vec<usize> = b1.difference(b2).copied().collect();
865 let diff2: Vec<usize> = b2.difference(b1).copied().collect();
866
867 if diff1.len() != 2 || diff2.len() != 2 {
868 continue;
869 }
870
871 let v1 = self.valuation.get(b1).copied().unwrap_or(f64::INFINITY);
873 let v2 = self.valuation.get(b2).copied().unwrap_or(f64::INFINITY);
874
875 let i = diff1[0];
877 let _j = diff1[1];
878 let a = diff2[0];
879 let b_elem = diff2[1];
880
881 let mut exchange1 = b1.clone();
883 exchange1.remove(&i);
884 exchange1.insert(a);
885
886 let mut exchange2 = b1.clone();
887 exchange2.remove(&i);
888 exchange2.insert(b_elem);
889
890 let v_ex1 = self
891 .valuation
892 .get(&exchange1)
893 .copied()
894 .unwrap_or(f64::INFINITY);
895 let v_ex2 = self
896 .valuation
897 .get(&exchange2)
898 .copied()
899 .unwrap_or(f64::INFINITY);
900
901 if v1.is_finite() && v2.is_finite() && v_ex1.is_infinite() && v_ex2.is_infinite() {
903 return false;
904 }
905 }
906 }
907
908 true
909 }
910
911 #[cfg(feature = "tropical-schubert")]
915 #[must_use]
916 pub fn to_tropical_schubert(&self) -> crate::tropical_schubert::TropicalSchubertClass {
917 let weights: Vec<i64> = self.valuation.values().map(|&v| v as i64).collect();
919
920 crate::tropical_schubert::TropicalSchubertClass::new(weights)
921 }
922}
923
924fn k_subsets_btree(n: usize, k: usize) -> BTreeSet<BTreeSet<usize>> {
927 k_subsets_vec(n, k)
928 .into_iter()
929 .map(|s| s.into_iter().collect())
930 .collect()
931}
932
933fn k_subsets_vec(n: usize, k: usize) -> Vec<Vec<usize>> {
934 let mut result = Vec::new();
935 let mut current = Vec::with_capacity(k);
936 gen_subsets(n, k, 0, &mut current, &mut result);
937 result
938}
939
940fn gen_subsets(
941 n: usize,
942 k: usize,
943 start: usize,
944 current: &mut Vec<usize>,
945 result: &mut Vec<Vec<usize>>,
946) {
947 if current.len() == k {
948 result.push(current.clone());
949 return;
950 }
951 let remaining = k - current.len();
952 if start + remaining > n {
953 return;
954 }
955 for i in start..=(n - remaining) {
956 current.push(i);
957 gen_subsets(n, k, i + 1, current, result);
958 current.pop();
959 }
960}
961
962fn subsets_of_size(n: usize, k: usize) -> Vec<BTreeSet<usize>> {
963 k_subsets_vec(n, k)
964 .into_iter()
965 .map(|s| s.into_iter().collect())
966 .collect()
967}
968
969#[cfg(feature = "parallel")]
971#[must_use]
972pub fn circuits_batch(matroids: &[Matroid]) -> Vec<BTreeSet<BTreeSet<usize>>> {
973 use rayon::prelude::*;
974 matroids.par_iter().map(|m| m.circuits()).collect()
975}
976
977#[cfg(feature = "parallel")]
979#[must_use]
980pub fn tutte_polynomial_batch(matroids: &[Matroid]) -> Vec<BTreeMap<(usize, usize), i64>> {
981 use rayon::prelude::*;
982 matroids.par_iter().map(|m| m.tutte_polynomial()).collect()
983}
984
985#[cfg(feature = "parallel")]
987#[must_use]
988pub fn intersection_cardinality_batch(pairs: &[(Matroid, Matroid)]) -> Vec<usize> {
989 use rayon::prelude::*;
990 pairs
991 .par_iter()
992 .map(|(m1, m2)| m1.intersection_cardinality(m2))
993 .collect()
994}
995
996#[cfg(test)]
997mod tests {
998 use super::*;
999 use crate::namespace::Capability;
1000 use crate::schubert::SchubertClass;
1001
1002 #[test]
1003 fn test_uniform_matroid() {
1004 let m = Matroid::uniform(2, 4);
1005 assert_eq!(m.rank, 2);
1006 assert_eq!(m.bases.len(), 6); assert_eq!(m.ground_set_size, 4);
1008 }
1009
1010 #[test]
1011 fn test_matroid_from_plucker() {
1012 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();
1015 assert_eq!(m.rank, 2);
1016 assert_eq!(m.bases.len(), 6);
1017 }
1018
1019 #[test]
1020 fn test_matroid_from_plucker_partial() {
1021 let coords = vec![1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
1023 let m = Matroid::from_plucker(2, 4, &coords, 0.001).unwrap();
1024 assert_eq!(m.rank, 2);
1025 assert_eq!(m.bases.len(), 4);
1026 }
1027
1028 #[test]
1029 fn test_schubert_matroid() {
1030 let m = Matroid::schubert_matroid(&[1], 2, 4).unwrap();
1034 assert_eq!(m.rank, 2);
1035 assert_eq!(m.bases.len(), 3);
1036 }
1037
1038 #[test]
1039 fn test_schubert_matroid_empty_partition() {
1040 let m = Matroid::schubert_matroid(&[], 2, 4).unwrap();
1042 assert_eq!(m.bases.len(), 6);
1043 }
1044
1045 #[test]
1046 fn test_matroid_circuits() {
1047 let m = Matroid::uniform(2, 4);
1049 let circuits = m.circuits();
1050 assert_eq!(circuits.len(), 4); }
1052
1053 #[test]
1054 fn test_matroid_dual() {
1055 let m = Matroid::uniform(2, 4);
1056 let dual = m.dual();
1057 assert_eq!(dual.rank, 2); assert_eq!(dual.bases.len(), 6); }
1060
1061 #[test]
1062 fn test_matroid_direct_sum() {
1063 let m1 = Matroid::uniform(1, 2);
1064 let m2 = Matroid::uniform(1, 2);
1065 let sum = m1.direct_sum(&m2);
1066 assert_eq!(sum.rank, 2);
1067 assert_eq!(sum.ground_set_size, 4);
1068 }
1069
1070 #[test]
1071 fn test_matroid_loop_coloop() {
1072 let m = Matroid::uniform(2, 3);
1074 for e in 0..3 {
1075 assert!(!m.is_loop(e));
1076 assert!(!m.is_coloop(e));
1077 }
1078 }
1079
1080 #[test]
1081 fn test_matroid_delete_contract() {
1082 let m = Matroid::uniform(2, 4);
1083
1084 let deleted = m.delete(0);
1085 assert_eq!(deleted.ground_set_size, 3);
1086 assert_eq!(deleted.rank, 2);
1087
1088 let contracted = m.contract(0);
1089 assert_eq!(contracted.ground_set_size, 3);
1090 assert_eq!(contracted.rank, 1);
1091 }
1092
1093 #[test]
1094 fn test_matroid_intersection_cardinality() {
1095 let m1 = Matroid::uniform(2, 4);
1096 let m2 = Matroid::uniform(2, 4);
1097 assert_eq!(m1.intersection_cardinality(&m2), 2);
1099 }
1100
1101 #[test]
1102 fn test_tutte_polynomial_uniform() {
1103 let m = Matroid::uniform(1, 2);
1104 let tutte = m.tutte_polynomial();
1105 assert_eq!(tutte.get(&(1, 0)).copied().unwrap_or(0), 1);
1107 assert_eq!(tutte.get(&(0, 1)).copied().unwrap_or(0), 1);
1108 }
1109
1110 #[test]
1111 fn test_namespace_capability_matroid() {
1112 let pos = SchubertClass::new(vec![], (2, 4)).unwrap();
1113 let mut ns = Namespace::new("test", pos);
1114
1115 let cap1 = Capability::new("c1", "Cap 1", vec![1], (2, 4)).unwrap();
1116 let cap2 = Capability::new("c2", "Cap 2", vec![1], (2, 4)).unwrap();
1117 ns.grant(cap1).unwrap();
1118 ns.grant(cap2).unwrap();
1119
1120 let matroid = ns.capability_matroid();
1121 assert!(matroid.is_some());
1122 let m = matroid.unwrap();
1123 assert_eq!(m.ground_set_size, 2);
1124 }
1125
1126 #[test]
1127 fn test_valuated_matroid_tropical_plucker() {
1128 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();
1130 assert_eq!(vm.matroid.rank, 2);
1131 assert!(vm.satisfies_tropical_plucker());
1132 }
1133
1134 #[cfg(feature = "parallel")]
1135 #[test]
1136 fn test_circuits_batch() {
1137 let matroids = vec![Matroid::uniform(2, 4), Matroid::uniform(1, 3)];
1138 let results = super::circuits_batch(&matroids);
1139 assert_eq!(results[0].len(), 4); assert_eq!(results[1].len(), 3); }
1142
1143 #[cfg(feature = "parallel")]
1144 #[test]
1145 fn test_tutte_polynomial_batch() {
1146 let matroids = vec![Matroid::uniform(1, 2), Matroid::uniform(1, 3)];
1147 let results = super::tutte_polynomial_batch(&matroids);
1148 assert_eq!(results.len(), 2);
1149 assert_eq!(results[0].get(&(1, 0)).copied().unwrap_or(0), 1);
1151 assert_eq!(results[0].get(&(0, 1)).copied().unwrap_or(0), 1);
1152 }
1153
1154 #[cfg(feature = "parallel")]
1155 #[test]
1156 fn test_intersection_cardinality_batch() {
1157 let pairs = vec![
1158 (Matroid::uniform(2, 4), Matroid::uniform(2, 4)),
1159 (Matroid::uniform(1, 3), Matroid::uniform(1, 3)),
1160 ];
1161 let results = super::intersection_cardinality_batch(&pairs);
1162 assert_eq!(results, vec![2, 1]);
1163 }
1164}