1use crate::littlewood_richardson::{lr_coefficient, schubert_product, Partition};
21use crate::namespace::Namespace;
22use std::collections::BTreeMap;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct CSMClass {
33 pub coefficients: BTreeMap<Partition, i64>,
35 pub grassmannian: (usize, usize),
37}
38
39impl CSMClass {
40 pub fn of_schubert_cell(partition: &[usize], grassmannian: (usize, usize)) -> Self {
58 let (_k, _n) = grassmannian;
59
60 if partition.is_empty() || partition.iter().all(|&p| p == 0) {
61 let mut coefficients = BTreeMap::new();
63 coefficients.insert(Partition::empty(), 1);
64 return CSMClass {
65 coefficients,
66 grassmannian,
67 };
68 }
69
70 let mut coefficients = BTreeMap::new();
75 let part = Partition::new(partition.to_vec());
76 coefficients.insert(part, 1);
77
78 for i in 0..partition.len() {
81 if partition[i] > 0 {
82 let mut smaller = partition.to_vec();
83 smaller[i] -= 1;
84 let smaller_part = Partition::new(smaller);
85 if !smaller_part.parts.is_empty() {
86 *coefficients.entry(smaller_part).or_insert(0) += 1;
87 }
88 }
89 }
90
91 CSMClass {
92 coefficients,
93 grassmannian,
94 }
95 }
96
97 pub fn of_schubert_variety(partition: &[usize], grassmannian: (usize, usize)) -> Self {
112 let (k, n) = grassmannian;
113 let m = n - k;
114
115 let mut total = BTreeMap::new();
117
118 let cell_csm = Self::of_schubert_cell(partition, grassmannian);
121 for (part, coeff) in &cell_csm.coefficients {
122 *total.entry(part.clone()).or_insert(0) += coeff;
123 }
124
125 let part = Partition::new(partition.to_vec());
128 let cells_in_closure = partitions_dominated_by(&part, k, m);
129
130 for mu in cells_in_closure {
131 if mu != part {
132 let mu_csm = Self::of_schubert_cell(&mu.parts, grassmannian);
133 for (p, c) in &mu_csm.coefficients {
134 *total.entry(p.clone()).or_insert(0) += c;
135 }
136 }
137 }
138
139 CSMClass {
140 coefficients: total,
141 grassmannian,
142 }
143 }
144
145 #[must_use]
156 pub fn euler_characteristic(&self) -> i64 {
157 let (k, n) = self.grassmannian;
158 let m = n - k;
159 let point_class = Partition::new(vec![m; k]);
160
161 let mut euler = 0i64;
164
165 for (partition, &coeff) in &self.coefficients {
166 if coeff == 0 {
167 continue;
168 }
169
170 let codim_lambda = partition.size();
173 let codim_needed = k * m - codim_lambda;
174
175 if codim_needed == 0 {
176 euler += coeff;
178 }
179 }
180
181 if euler == 0 {
183 for (partition, &coeff) in &self.coefficients {
184 if coeff == 0 {
185 continue;
186 }
187 let dual = dual_partition(partition, k, m);
189 if let Some(d) = dual {
190 let lr = lr_coefficient(partition, &d, &point_class);
191 euler += coeff * lr as i64;
192 }
193 }
194 }
195
196 euler
197 }
198
199 #[must_use]
211 pub fn csm_intersection(&self, other: &CSMClass) -> CSMClass {
212 assert_eq!(
213 self.grassmannian, other.grassmannian,
214 "CSM classes must be on the same Grassmannian"
215 );
216
217 let (k, n) = self.grassmannian;
218 let mut result = BTreeMap::new();
219
220 for (lambda, &a) in &self.coefficients {
221 if a == 0 {
222 continue;
223 }
224 for (mu, &b) in &other.coefficients {
225 if b == 0 {
226 continue;
227 }
228 let products = schubert_product(lambda, mu, (k, n));
230 for (nu, lr) in products {
231 *result.entry(nu).or_insert(0i64) += a * b * lr as i64;
232 }
233 }
234 }
235
236 CSMClass {
237 coefficients: result,
238 grassmannian: self.grassmannian,
239 }
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct SegreClass {
249 pub coefficients: BTreeMap<Partition, i64>,
251 pub grassmannian: (usize, usize),
253}
254
255impl SegreClass {
256 #[must_use]
266 pub fn from_chern(csm: &CSMClass) -> Self {
267 let (k, n) = csm.grassmannian;
268 let m = n - k;
269
270 let mut segre = BTreeMap::new();
272 segre.insert(Partition::empty(), 1i64);
273
274 for codim in 1..=(k * m) {
277 let mut s_codim = 0i64;
278 for (part, &c_val) in &csm.coefficients {
279 let c_codim = part.size();
280 if c_codim == 0 || c_codim > codim {
281 continue;
282 }
283 let remaining_codim = codim - c_codim;
284 for (s_part, &s_val) in &segre.clone() {
286 if s_part.size() == remaining_codim {
287 let products = schubert_product(part, s_part, (k, n));
289 for (nu, lr) in products {
290 if nu.size() == codim {
291 s_codim -= c_val * s_val * lr as i64;
292 }
293 }
294 }
295 }
296 }
297 if s_codim != 0 {
298 let parts = partitions_of_size(codim, k, m);
300 if parts.len() == 1 {
301 segre.insert(parts[0].clone(), s_codim);
302 } else if !parts.is_empty() {
303 segre.insert(parts[0].clone(), s_codim);
305 }
306 }
307 }
308
309 SegreClass {
310 coefficients: segre,
311 grassmannian: csm.grassmannian,
312 }
313 }
314
315 #[must_use]
329 pub fn excess_intersection(&self, excess_dim: usize) -> i64 {
330 self.coefficients
332 .iter()
333 .filter(|(p, _)| p.size() == excess_dim)
334 .map(|(_, &c)| c)
335 .sum()
336 }
337}
338
339impl Namespace {
341 #[must_use]
352 pub fn is_degenerate(&self) -> bool {
353 let csm = CSMClass::of_schubert_cell(&self.position.partition, self.grassmannian);
354 let euler = csm.euler_characteristic();
356 euler != 1
357 }
358
359 #[must_use]
371 pub fn csm_count_configurations(&self) -> i64 {
372 if self.capabilities.is_empty() {
373 return 1;
374 }
375
376 let (k, n) = self.grassmannian;
377 let gr_dim = k * (n - k);
378
379 let total_codim: usize = self.capabilities.iter().map(|c| c.codimension()).sum();
381
382 if total_codim > gr_dim {
383 return 0;
384 }
385
386 let mut product = CSMClass::of_schubert_cell(
388 &self.capabilities[0].schubert_class.partition,
389 self.grassmannian,
390 );
391
392 for cap in &self.capabilities[1..] {
393 let cap_csm =
394 CSMClass::of_schubert_cell(&cap.schubert_class.partition, self.grassmannian);
395 product = product.csm_intersection(&cap_csm);
396 }
397
398 product.euler_characteristic()
399 }
400}
401
402fn partitions_dominated_by(lambda: &Partition, k: usize, m: usize) -> Vec<Partition> {
406 let mut result = Vec::new();
407 generate_dominated(lambda, k, m, &[], 0, &mut result);
408 result
409}
410
411fn generate_dominated(
412 lambda: &Partition,
413 k: usize,
414 m: usize,
415 prefix: &[usize],
416 row: usize,
417 result: &mut Vec<Partition>,
418) {
419 if row >= k {
420 let part = Partition::new(prefix.to_vec());
421 result.push(part);
422 return;
423 }
424
425 let max_val = lambda.parts.get(row).copied().unwrap_or(0).min(m);
426 let prev = if row > 0 {
427 prefix.get(row - 1).copied().unwrap_or(m)
428 } else {
429 m
430 };
431 let upper = max_val.min(prev);
432
433 for v in 0..=upper {
434 let mut new_prefix = prefix.to_vec();
435 new_prefix.push(v);
436 generate_dominated(lambda, k, m, &new_prefix, row + 1, result);
437 }
438}
439
440fn dual_partition(lambda: &Partition, k: usize, m: usize) -> Option<Partition> {
442 let mut padded = vec![0usize; k];
443 for (i, &p) in lambda.parts.iter().enumerate() {
444 if i >= k {
445 return None;
446 }
447 if p > m {
448 return None;
449 }
450 padded[i] = p;
451 }
452
453 let dual_parts: Vec<usize> = (0..k).map(|i| m - padded[k - 1 - i]).collect();
454
455 Some(Partition::new(dual_parts))
456}
457
458fn partitions_of_size(size: usize, k: usize, m: usize) -> Vec<Partition> {
460 let mut result = Vec::new();
461 gen_partitions(size, k, m, &[], 0, &mut result);
462 result
463}
464
465fn gen_partitions(
466 remaining: usize,
467 k: usize,
468 m: usize,
469 prefix: &[usize],
470 row: usize,
471 result: &mut Vec<Partition>,
472) {
473 if remaining == 0 {
474 result.push(Partition::new(prefix.to_vec()));
475 return;
476 }
477 if row >= k {
478 return;
479 }
480 let prev = if row > 0 { prefix[row - 1] } else { m };
481 let max_val = remaining.min(prev).min(m);
482 for v in (1..=max_val).rev() {
483 let mut new_prefix = prefix.to_vec();
484 new_prefix.push(v);
485 gen_partitions(remaining - v, k, m, &new_prefix, row + 1, result);
486 }
487}
488
489#[cfg(feature = "parallel")]
491#[must_use]
492pub fn csm_of_cells_batch(
493 partitions: &[Vec<usize>],
494 grassmannian: (usize, usize),
495) -> Vec<CSMClass> {
496 use rayon::prelude::*;
497 partitions
498 .par_iter()
499 .map(|part| CSMClass::of_schubert_cell(part, grassmannian))
500 .collect()
501}
502
503#[cfg(feature = "parallel")]
505#[must_use]
506pub fn euler_characteristic_batch(classes: &[CSMClass]) -> Vec<i64> {
507 use rayon::prelude::*;
508 classes
509 .par_iter()
510 .map(|csm| csm.euler_characteristic())
511 .collect()
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517 use crate::schubert::SchubertClass;
518
519 #[test]
520 fn test_csm_class_point() {
521 let csm = CSMClass::of_schubert_cell(&[2, 2], (2, 4));
523 assert!(csm.coefficients.contains_key(&Partition::new(vec![2, 2])));
524 }
525
526 #[test]
527 fn test_csm_top_cell() {
528 let csm = CSMClass::of_schubert_cell(&[], (2, 4));
530 assert_eq!(csm.coefficients.get(&Partition::empty()), Some(&1));
531 }
532
533 #[test]
534 fn test_csm_euler_characteristic_top_cell() {
535 let csm = CSMClass::of_schubert_cell(&[], (2, 4));
536 let euler = csm.euler_characteristic();
537 assert_eq!(euler, 1);
539 }
540
541 #[test]
542 fn test_csm_variety() {
543 let csm = CSMClass::of_schubert_variety(&[1], (2, 4));
546 assert!(!csm.coefficients.is_empty());
548 }
549
550 #[test]
551 fn test_csm_intersection() {
552 let csm1 = CSMClass::of_schubert_cell(&[1], (2, 4));
553 let csm2 = CSMClass::of_schubert_cell(&[1], (2, 4));
554 let product = csm1.csm_intersection(&csm2);
555 assert!(!product.coefficients.is_empty());
557 }
558
559 #[test]
560 fn test_segre_from_chern() {
561 let csm = CSMClass::of_schubert_cell(&[], (2, 4));
562 let segre = SegreClass::from_chern(&csm);
563 assert_eq!(segre.coefficients.get(&Partition::empty()), Some(&1));
565 }
566
567 #[test]
568 fn test_csm_agrees_transverse() {
569 let pos = SchubertClass::new(vec![], (2, 4)).unwrap();
571 let mut ns = Namespace::new("test", pos);
572
573 let cap1 = crate::namespace::Capability::new("c1", "Cap1", vec![1], (2, 4)).unwrap();
574 let cap2 = crate::namespace::Capability::new("c2", "Cap2", vec![1], (2, 4)).unwrap();
575 let cap3 = crate::namespace::Capability::new("c3", "Cap3", vec![1], (2, 4)).unwrap();
576 let cap4 = crate::namespace::Capability::new("c4", "Cap4", vec![1], (2, 4)).unwrap();
577 ns.grant(cap1).unwrap();
578 ns.grant(cap2).unwrap();
579 ns.grant(cap3).unwrap();
580 ns.grant(cap4).unwrap();
581
582 let csm_count = ns.csm_count_configurations();
584 assert!(csm_count > 0);
587 }
588
589 #[test]
590 fn test_namespace_is_degenerate() {
591 let pos = SchubertClass::new(vec![], (2, 4)).unwrap();
592 let ns = Namespace::new("test", pos);
593 assert!(!ns.is_degenerate());
595 }
596
597 #[test]
598 fn test_dual_partition() {
599 let lambda = Partition::new(vec![2, 1]);
600 let dual = dual_partition(&lambda, 2, 2);
601 assert!(dual.is_some());
602 let d = dual.unwrap();
603 assert_eq!(d, Partition::new(vec![1]));
605 }
606
607 #[cfg(feature = "parallel")]
608 #[test]
609 fn test_csm_of_cells_batch() {
610 let partitions = vec![vec![], vec![1], vec![2, 1]];
611 let results = super::csm_of_cells_batch(&partitions, (2, 4));
612 assert_eq!(results.len(), 3);
613 assert_eq!(results[0].coefficients.get(&Partition::empty()), Some(&1));
615 }
616
617 #[cfg(feature = "parallel")]
618 #[test]
619 fn test_euler_characteristic_batch() {
620 let classes = vec![
621 CSMClass::of_schubert_cell(&[], (2, 4)),
622 CSMClass::of_schubert_cell(&[1], (2, 4)),
623 ];
624 let results = super::euler_characteristic_batch(&classes);
625 assert_eq!(results.len(), 2);
626 assert_eq!(results[0], 1);
628 }
629}