Skip to main content

karpal_schubert_types/
intersection.rs

1use amari_enumerative::{IntersectionResult, SchubertCalculus};
2
3use crate::schubert_type::SchubertType;
4
5/// Result of intersecting two Schubert types.
6#[derive(Debug, Clone)]
7pub struct Intersection {
8    kind: IntersectionKind,
9    multiplicity: u64,
10    /// When the result is positive-dimensional, this carries the
11    /// decomposition into Schubert classes if computable.
12    decomposition: Vec<SchubertType>,
13}
14
15impl Intersection {
16    fn structural_zero() -> Self {
17        Self {
18            kind: IntersectionKind::StructuralZero,
19            multiplicity: 0,
20            decomposition: Vec::new(),
21        }
22    }
23
24    fn geometric_zero() -> Self {
25        Self {
26            kind: IntersectionKind::GeometricZero,
27            multiplicity: 0,
28            decomposition: Vec::new(),
29        }
30    }
31
32    fn positive(multiplicity: u64, decomposition: Vec<SchubertType>) -> Self {
33        Self {
34            kind: IntersectionKind::Positive,
35            multiplicity,
36            decomposition,
37        }
38    }
39
40    /// The classification of this intersection.
41    pub fn kind(&self) -> IntersectionKind {
42        self.kind
43    }
44
45    /// The intersection multiplicity (0 for zeros and underdetermined).
46    pub fn multiplicity(&self) -> u64 {
47        self.multiplicity
48    }
49
50    /// Decomposition into Schubert classes, if available.
51    pub fn decomposition(&self) -> &[SchubertType] {
52        &self.decomposition
53    }
54
55    /// When the intersection is positive, attempt to extract the
56    /// resulting Schubert type (the first one in the decomposition).
57    pub fn into_schubert(self) -> Option<SchubertType> {
58        if self.kind == IntersectionKind::Positive && !self.decomposition.is_empty() {
59            Some(self.decomposition.into_iter().next().unwrap())
60        } else {
61            None
62        }
63    }
64}
65
66/// Classification of an intersection result.
67///
68/// - `StructuralZero`: total codimension exceeds Grassmannian dimension
69/// - `GeometricZero`: correctly dimensioned but no intersection points
70/// - `Positive`: nonempty intersection with known multiplicity
71/// - `Underdetermined`: the computation could not resolve the result
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum IntersectionKind {
74    StructuralZero,
75    GeometricZero,
76    Positive,
77    Underdetermined,
78}
79
80impl IntersectionKind {
81    /// True for either kind of empty intersection.
82    pub fn is_zero(&self) -> bool {
83        matches!(self, Self::StructuralZero | Self::GeometricZero)
84    }
85}
86
87/// Compute the intersection of two Schubert types.
88///
89/// Uses `amari-enumerative`'s Schubert calculus engine to compute the
90/// intersection product and classify the result.
91pub fn check_intersection(a: &SchubertType, b: &SchubertType) -> Intersection {
92    let mut calc = SchubertCalculus::new(a.grassmannian_dim());
93
94    // Use multi_intersect for the two classes
95    let classes = [a.as_inner().clone(), b.as_inner().clone()];
96    let result = calc.multi_intersect(&classes);
97
98    match result {
99        IntersectionResult::Empty => {
100            // Distinguish structural vs geometric zero
101            let total_codim = a.codimension() + b.codimension();
102            let dim = calc.grassmannian_dimension();
103            if total_codim > dim {
104                Intersection::structural_zero()
105            } else {
106                Intersection::geometric_zero()
107            }
108        }
109        IntersectionResult::Finite(n) => Intersection::positive(n, Vec::new()),
110        IntersectionResult::PositiveDimensional {
111            dimension: _,
112            degree,
113        } => {
114            // Positive-dimensional intersection is always non-empty
115            let multiplicity = degree.unwrap_or(1);
116            Intersection::positive(multiplicity, Vec::new())
117        }
118    }
119}