Skip to main content

oxilean_std/birational_geometry/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
7
8/// Data for Kawamata-Viehweg vanishing theorem.
9#[allow(dead_code)]
10#[derive(Debug, Clone)]
11pub struct KVVanishingData {
12    /// Variety.
13    pub variety: String,
14    /// Dimension.
15    pub dim: usize,
16    /// Whether the nef and big conditions hold.
17    pub is_nef_big: bool,
18    /// The degree i at which H^i(K_X + L) = 0.
19    pub vanishing_degrees: Vec<usize>,
20}
21#[allow(dead_code)]
22impl KVVanishingData {
23    /// Creates KV vanishing data.
24    pub fn new(variety: &str, dim: usize) -> Self {
25        KVVanishingData {
26            variety: variety.to_string(),
27            dim,
28            is_nef_big: false,
29            vanishing_degrees: Vec::new(),
30        }
31    }
32    /// Marks the line bundle as nef and big.
33    pub fn nef_and_big(mut self) -> Self {
34        self.is_nef_big = true;
35        self.vanishing_degrees = (1..=self.dim).collect();
36        self
37    }
38    /// Returns the KV vanishing statement.
39    pub fn vanishing_statement(&self) -> String {
40        if self.is_nef_big {
41            format!("H^i(K_X + L) = 0 for i > 0 on {}", self.variety)
42        } else {
43            format!(
44                "KV vanishing not applicable: L not nef+big on {}",
45                self.variety
46            )
47        }
48    }
49    /// Checks if degree i vanishes.
50    pub fn vanishes_at(&self, i: usize) -> bool {
51        self.vanishing_degrees.contains(&i)
52    }
53}
54/// A log pair (X, Δ) consisting of a variety and an effective boundary divisor.
55#[derive(Debug, Clone)]
56pub struct LogPair {
57    /// The underlying variety.
58    pub variety: String,
59    /// Coefficients of the boundary divisor Δ = ∑ a_i D_i (each a_i ∈ [0, 1]).
60    pub boundary_coeffs: Vec<f64>,
61    /// Names of the boundary divisors.
62    pub boundary_components: Vec<String>,
63}
64impl LogPair {
65    /// Create a log pair with no boundary.
66    pub fn trivial(variety: impl Into<String>) -> Self {
67        LogPair {
68            variety: variety.into(),
69            boundary_coeffs: vec![],
70            boundary_components: vec![],
71        }
72    }
73    /// Add a boundary component with coefficient a ∈ [0, 1].
74    pub fn with_boundary(mut self, name: impl Into<String>, coeff: f64) -> Self {
75        assert!(
76            (0.0..=1.0).contains(&coeff),
77            "Boundary coefficients must be in [0, 1]"
78        );
79        self.boundary_components.push(name.into());
80        self.boundary_coeffs.push(coeff);
81        self
82    }
83    /// Check if (X, Δ) is log-canonical (all coefficients ≤ 1).
84    pub fn is_log_canonical(&self) -> bool {
85        self.boundary_coeffs.iter().all(|&a| a <= 1.0 + 1e-10)
86    }
87    /// Check if (X, Δ) is klt (all coefficients < 1).
88    pub fn is_klt(&self) -> bool {
89        self.boundary_coeffs.iter().all(|&a| a < 1.0 - 1e-10)
90    }
91    /// Check if (X, Δ) is plt (purely log-terminal): reduced components can have coeff = 1.
92    pub fn is_plt(&self) -> bool {
93        self.is_log_canonical()
94    }
95    /// Total degree of the boundary ∑ a_i.
96    pub fn boundary_degree(&self) -> f64 {
97        self.boundary_coeffs.iter().sum()
98    }
99}
100/// Blow-up data: records what was blown up and the resulting variety.
101#[derive(Debug, Clone)]
102pub struct BlowUpData {
103    /// The original variety.
104    pub original: String,
105    /// The center of the blow-up (subvariety or ideal).
106    pub center: String,
107    /// Codimension of the center.
108    pub center_codim: usize,
109    /// The exceptional divisor E ≅ P^{r-1}-bundle over the center.
110    pub exceptional_divisor: String,
111}
112impl BlowUpData {
113    /// Blow up a smooth variety along a smooth center of codimension r.
114    pub fn new(original: impl Into<String>, center: impl Into<String>, codim: usize) -> Self {
115        let orig = original.into();
116        let c = center.into();
117        BlowUpData {
118            exceptional_divisor: format!("E = P(N_{{{}/{}}})", c, orig),
119            original: orig,
120            center: c,
121            center_codim: codim,
122        }
123    }
124    /// The self-intersection number E^{r-1} on the blow-up equals (-1)^{r-1} deg(center).
125    pub fn exceptional_self_intersection(&self) -> i64 {
126        let r = self.center_codim;
127        if r == 0 {
128            return 1;
129        }
130        if (r - 1) % 2 == 0 {
131            1i64
132        } else {
133            -1i64
134        }
135    }
136    /// The discrepancy of the exceptional divisor on the smooth blow-up: a(E) = r - 1.
137    pub fn exceptional_discrepancy(&self) -> i64 {
138        self.center_codim as i64 - 1
139    }
140}
141/// Type of MMP operation.
142#[allow(dead_code)]
143#[derive(Debug, Clone, PartialEq)]
144pub enum MMPOperation {
145    /// Divisorial contraction.
146    DivisorialContraction,
147    /// Flip.
148    Flip,
149    /// Mori fiber space (final step).
150    MoriFiberSpace,
151    /// Minimal model (K_X nef).
152    MinimalModel,
153}
154/// Minimal model program step: classify the type of the contraction.
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum MMPStep {
157    /// Contract a divisor E to a variety of strictly smaller Picard number.
158    DivisorialContraction { contracted_divisor: String },
159    /// Replace a flipping contraction by its flip.
160    Flip { flipping_locus: String },
161    /// Reached a minimal model (K_X nef).
162    MinimalModel,
163    /// X is uniruled and MMP terminates in a Fano fibration.
164    FanoFibration { base: String },
165}
166impl MMPStep {
167    /// Description of the MMP step.
168    pub fn description(&self) -> String {
169        match self {
170            MMPStep::DivisorialContraction { contracted_divisor } => {
171                format!("Divisorial contraction: contract {}", contracted_divisor)
172            }
173            MMPStep::Flip { flipping_locus } => {
174                format!("Flip: flip over {}", flipping_locus)
175            }
176            MMPStep::MinimalModel => "Minimal model reached: K_X is nef".to_string(),
177            MMPStep::FanoFibration { base } => format!("Fano fibration over {}", base),
178        }
179    }
180    /// Check if this step terminates the MMP.
181    pub fn is_terminal(&self) -> bool {
182        matches!(self, MMPStep::MinimalModel | MMPStep::FanoFibration { .. })
183    }
184}
185/// Classification of the contraction associated with an extremal ray.
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub enum ContractionType {
188    /// Contracts a divisor to a lower-dimensional variety.
189    Divisorial,
190    /// Small contraction: flipping locus has codimension ≥ 2.
191    Small,
192    /// Fiber type: target has strictly smaller dimension.
193    FiberType,
194}
195/// A step in the Minimal Model Program (MMP).
196///
197/// Encodes the four possible outcomes of a single MMP step:
198/// divisorial contraction, flip, reaching a minimal model, or arriving at a Mori fiber space.
199#[derive(Debug, Clone, PartialEq, Eq)]
200pub enum MmpStep {
201    /// Contract a divisor E: reduces Picard number by 1.
202    Contraction {
203        /// The divisor being contracted.
204        divisor: String,
205        /// The type of the target singularity after contraction.
206        singularity_type: SingularityType,
207    },
208    /// Replace a flipping contraction by its flip.
209    Flip {
210        /// Description of the flipping locus.
211        locus: String,
212    },
213    /// A flopping contraction followed by its flop.
214    Flop {
215        /// Description of the flopping locus.
216        locus: String,
217    },
218    /// Terminate: K_X is nef (minimal model reached).
219    MinimalModel,
220    /// Terminate: Mori fiber space structure X → Z.
221    FiberSpace {
222        /// The base of the fiber space.
223        base: String,
224        /// Dimension of the fiber.
225        fiber_dim: usize,
226    },
227}
228impl MmpStep {
229    /// Human-readable description of the step.
230    pub fn description(&self) -> String {
231        match self {
232            MmpStep::Contraction {
233                divisor,
234                singularity_type,
235            } => {
236                format!(
237                    "Divisorial contraction of {} (→ {} singularities)",
238                    divisor,
239                    singularity_type.name()
240                )
241            }
242            MmpStep::Flip { locus } => format!("Flip over {}", locus),
243            MmpStep::Flop { locus } => format!("Flop over {}", locus),
244            MmpStep::MinimalModel => "Minimal model reached (K_X nef)".to_string(),
245            MmpStep::FiberSpace { base, fiber_dim } => {
246                format!("Mori fiber space over {} (fiber dim = {})", base, fiber_dim)
247            }
248        }
249    }
250    /// Whether the step terminates the MMP.
251    pub fn is_terminal(&self) -> bool {
252        matches!(self, MmpStep::MinimalModel | MmpStep::FiberSpace { .. })
253    }
254}
255/// Data for a log pair (X, Δ) in birational geometry.
256#[allow(dead_code)]
257#[derive(Debug, Clone)]
258pub struct LogPairData {
259    /// Variety X.
260    pub variety: String,
261    /// Boundary divisor Δ = sum a_i D_i.
262    pub boundary_components: Vec<(String, f64)>,
263    /// Log discrepancies (for singularity type).
264    pub log_discrepancies: Vec<f64>,
265}
266#[allow(dead_code)]
267impl LogPairData {
268    /// Creates a log pair.
269    pub fn new(variety: &str) -> Self {
270        LogPairData {
271            variety: variety.to_string(),
272            boundary_components: Vec::new(),
273            log_discrepancies: Vec::new(),
274        }
275    }
276    /// Adds a boundary component a_i D_i.
277    pub fn add_boundary(&mut self, divisor: &str, coeff: f64) {
278        self.boundary_components.push((divisor.to_string(), coeff));
279    }
280    /// Adds a log discrepancy.
281    pub fn add_log_discrepancy(&mut self, a: f64) {
282        self.log_discrepancies.push(a);
283    }
284    /// Returns the total boundary coefficient.
285    pub fn total_coefficient(&self) -> f64 {
286        self.boundary_components.iter().map(|(_, a)| a).sum()
287    }
288    /// Checks if the pair is Kawamata log terminal (klt): all log discrepancies > -1.
289    pub fn is_klt(&self) -> bool {
290        self.log_discrepancies.iter().all(|&a| a > -1.0)
291    }
292    /// Checks if the pair is log canonical (lc): all log discrepancies >= -1.
293    pub fn is_log_canonical(&self) -> bool {
294        self.log_discrepancies.iter().all(|&a| a >= -1.0)
295    }
296    /// Returns the singularity type string.
297    pub fn singularity_type(&self) -> &str {
298        if self.log_discrepancies.is_empty() {
299            "smooth"
300        } else if self.is_klt() {
301            "klt (Kawamata log terminal)"
302        } else if self.is_log_canonical() {
303            "lc (log canonical)"
304        } else {
305            "worse than log canonical"
306        }
307    }
308}
309/// Zariski decomposition of a pseudoeffective divisor D = P + N.
310///
311/// - P is the nef part (intersection-theoretically trivial on curves in N).
312/// - N is the negative part (effective, whose support contains all negative-definite components).
313///
314/// This is a simplified representation storing divisor names and rational coefficients.
315#[derive(Debug, Clone)]
316pub struct ZariskiDecomp {
317    /// Original divisor name.
318    pub divisor: String,
319    /// Nef part P.
320    pub nef_part: Vec<(String, f64)>,
321    /// Negative part N.
322    pub neg_part: Vec<(String, f64)>,
323}
324impl ZariskiDecomp {
325    /// Construct a Zariski decomposition.
326    pub fn new(
327        divisor: impl Into<String>,
328        nef_part: Vec<(String, f64)>,
329        neg_part: Vec<(String, f64)>,
330    ) -> Self {
331        ZariskiDecomp {
332            divisor: divisor.into(),
333            nef_part,
334            neg_part,
335        }
336    }
337    /// Check that all N-coefficients are non-negative.
338    pub fn is_negative_part_effective(&self) -> bool {
339        self.neg_part.iter().all(|(_, c)| *c >= 0.0)
340    }
341    /// Total coefficient of the nef part.
342    pub fn nef_degree(&self) -> f64 {
343        self.nef_part.iter().map(|(_, c)| c).sum()
344    }
345    /// Total coefficient of the negative part.
346    pub fn neg_degree(&self) -> f64 {
347        self.neg_part.iter().map(|(_, c)| c).sum()
348    }
349}
350/// A projective variety represented by dimension and degree.
351#[derive(Debug, Clone, PartialEq, Eq)]
352pub struct ProjectiveVariety {
353    /// Dimension of the variety.
354    pub dim: usize,
355    /// Degree in projective space (self-intersection of hyperplane class).
356    pub degree: u64,
357    /// Name / description.
358    pub name: String,
359}
360impl ProjectiveVariety {
361    /// Create a new projective variety.
362    pub fn new(dim: usize, degree: u64, name: impl Into<String>) -> Self {
363        ProjectiveVariety {
364            dim,
365            degree,
366            name: name.into(),
367        }
368    }
369    /// Projective space P^n, which has degree 1.
370    pub fn projective_space(n: usize) -> Self {
371        ProjectiveVariety::new(n, 1, format!("P^{}", n))
372    }
373    /// A quadric hypersurface Q ⊂ P^{n+1} of dimension n and degree 2.
374    pub fn quadric(n: usize) -> Self {
375        ProjectiveVariety::new(n, 2, format!("Q^{}", n))
376    }
377    /// Del Pezzo surface of degree d (1 ≤ d ≤ 9).
378    /// S_d = P^2 blown up at (9-d) points in general position.
379    pub fn del_pezzo(d: usize) -> Self {
380        assert!((1..=9).contains(&d), "Del Pezzo degree must be 1..=9");
381        ProjectiveVariety::new(2, d as u64, format!("S_{}", d))
382    }
383}
384/// Kodaira dimension enum with -∞ = NegInfinity convention.
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum KodairaDim {
387    /// κ = -∞: uniruled varieties (covered by rational curves).
388    NegInfinity,
389    /// κ = k for k ≥ 0.
390    Finite(i64),
391}
392impl KodairaDim {
393    /// Construct κ = 0 (Calabi-Yau, abelian varieties, K3, Enriques, ...).
394    pub fn zero() -> Self {
395        KodairaDim::Finite(0)
396    }
397    /// Kodaira dimension of a product: κ(X × Y) = κ(X) + κ(Y).
398    pub fn product(self, other: KodairaDim) -> KodairaDim {
399        match (self, other) {
400            (KodairaDim::NegInfinity, _) | (_, KodairaDim::NegInfinity) => KodairaDim::NegInfinity,
401            (KodairaDim::Finite(a), KodairaDim::Finite(b)) => KodairaDim::Finite(a + b),
402        }
403    }
404    /// Whether this variety is of general type: κ = dim.
405    pub fn is_general_type(self, dim: usize) -> bool {
406        matches!(self, KodairaDim::Finite(k) if k == dim as i64)
407    }
408    /// Whether the variety is uniruled: κ = -∞.
409    pub fn is_uniruled(self) -> bool {
410        self == KodairaDim::NegInfinity
411    }
412    /// Human-readable classification.
413    pub fn classify(self, dim: usize) -> &'static str {
414        match self {
415            KodairaDim::NegInfinity => "uniruled",
416            KodairaDim::Finite(0) => "Kodaira dim 0 (CY / K3 / abelian type)",
417            KodairaDim::Finite(k) if k == dim as i64 => "general type",
418            KodairaDim::Finite(_) => "intermediate Kodaira dimension",
419        }
420    }
421}
422/// Represents a step in the Minimal Model Program.
423#[allow(dead_code)]
424#[derive(Debug, Clone)]
425pub struct MMPStepData {
426    /// Type of operation.
427    pub operation: MMPOperation,
428    /// Description of the resulting variety.
429    pub result_variety: String,
430    /// The exceptional divisor (for contractions).
431    pub exceptional_divisor: Option<String>,
432}
433#[allow(dead_code)]
434impl MMPStepData {
435    /// Creates an MMP step.
436    pub fn new(op: MMPOperation, result: &str) -> Self {
437        MMPStepData {
438            operation: op,
439            result_variety: result.to_string(),
440            exceptional_divisor: None,
441        }
442    }
443    /// Sets the exceptional divisor.
444    pub fn with_exceptional(mut self, div: &str) -> Self {
445        self.exceptional_divisor = Some(div.to_string());
446        self
447    }
448    /// Returns the description of this step.
449    pub fn description(&self) -> String {
450        let op_name = match &self.operation {
451            MMPOperation::DivisorialContraction => "Divisorial contraction",
452            MMPOperation::Flip => "Flip",
453            MMPOperation::MoriFiberSpace => "Mori fiber space",
454            MMPOperation::MinimalModel => "Minimal model",
455        };
456        format!("{} → {}", op_name, self.result_variety)
457    }
458    /// Checks if this is the final step.
459    pub fn is_final(&self) -> bool {
460        matches!(
461            &self.operation,
462            MMPOperation::MoriFiberSpace | MMPOperation::MinimalModel
463        )
464    }
465}
466/// Sarkisov link type.
467///
468/// The Sarkisov program decomposes every birational map between
469/// Mori fiber spaces into elementary links of type I–IV.
470#[derive(Debug, Clone, PartialEq, Eq)]
471pub enum SarkisovLinkType {
472    /// Type I: blow-up on the left side.
473    TypeI,
474    /// Type II: blow-up on both sides.
475    TypeII,
476    /// Type III: blow-down on the right side.
477    TypeIII,
478    /// Type IV: blow-down then blow-up without changing the base.
479    TypeIV,
480}
481impl SarkisovLinkType {
482    /// Description of the link type.
483    pub fn description(&self) -> &'static str {
484        match self {
485            SarkisovLinkType::TypeI => "Type I: blow-up (left), fiber space change",
486            SarkisovLinkType::TypeII => "Type II: blow-up (both), same base",
487            SarkisovLinkType::TypeIII => "Type III: blow-down (right), base change",
488            SarkisovLinkType::TypeIV => "Type IV: blow-down (right), flip base",
489        }
490    }
491    /// Number code (I=1, II=2, III=3, IV=4).
492    pub fn code(&self) -> u8 {
493        match self {
494            SarkisovLinkType::TypeI => 1,
495            SarkisovLinkType::TypeII => 2,
496            SarkisovLinkType::TypeIII => 3,
497            SarkisovLinkType::TypeIV => 4,
498        }
499    }
500}
501/// Iitaka fibration data for a variety of Kodaira dimension κ.
502#[allow(dead_code)]
503#[derive(Debug, Clone)]
504pub struct IitakaFibration {
505    /// Source variety.
506    pub source: String,
507    /// Base of the Iitaka fibration.
508    pub base: String,
509    /// Fiber description.
510    pub fiber: String,
511    /// Kodaira dimension of the source.
512    pub kodaira_dim: i64,
513    /// Dimension of the base (= kodaira_dim for Iitaka fibration).
514    pub base_dim: usize,
515}
516#[allow(dead_code)]
517impl IitakaFibration {
518    /// Creates Iitaka fibration data.
519    pub fn new(source: &str, base: &str, fiber: &str, kappa: i64) -> Self {
520        let base_dim = kappa.max(0) as usize;
521        IitakaFibration {
522            source: source.to_string(),
523            base: base.to_string(),
524            fiber: fiber.to_string(),
525            kodaira_dim: kappa,
526            base_dim,
527        }
528    }
529    /// Returns the Iitaka-Kodaira addition formula: κ(X) <= κ(F) + κ(B).
530    pub fn addition_formula(&self, kappa_fiber: i64, kappa_base: i64) -> bool {
531        self.kodaira_dim <= kappa_fiber + kappa_base
532    }
533    /// Returns a description of the fibration.
534    pub fn description(&self) -> String {
535        format!(
536            "{} → {} (base, κ={}) with fiber {}",
537            self.source, self.base, self.kodaira_dim, self.fiber
538        )
539    }
540    /// Checks if source is of general type (κ = dim).
541    pub fn is_general_type(&self, dim: usize) -> bool {
542        self.kodaira_dim == dim as i64
543    }
544}
545/// Mori cone information for a smooth Fano variety.
546///
547/// Stores the generators of the Mori cone NE(X) as curve classes (with K_X-degree).
548#[derive(Debug, Clone)]
549pub struct MoriCone {
550    /// Dimension of the variety.
551    pub dim: usize,
552    /// Extremal rays represented by K_X-degree (should be < 0).
553    pub extremal_rays: Vec<i64>,
554}
555impl MoriCone {
556    /// Create a Mori cone for projective space P^n with a single extremal ray (K_{P^n} · l = -(n+1)).
557    pub fn projective_space(n: usize) -> Self {
558        MoriCone {
559            dim: n,
560            extremal_rays: vec![-(n as i64 + 1)],
561        }
562    }
563    /// Create a Mori cone for a del Pezzo surface S_d.
564    /// S_9 = P^2 has one extremal ray; S_{9-k} has k more from the blown-up points.
565    pub fn del_pezzo(d: usize) -> Self {
566        let blown_up = 9 - d;
567        let mut rays: Vec<i64> = vec![-3];
568        rays.extend(std::iter::repeat(-1).take(blown_up));
569        MoriCone {
570            dim: 2,
571            extremal_rays: rays,
572        }
573    }
574    /// Number of extremal rays.
575    pub fn num_extremal_rays(&self) -> usize {
576        self.extremal_rays.len()
577    }
578    /// Check if all extremal rays are K-negative (Fano condition).
579    pub fn is_fano(&self) -> bool {
580        self.extremal_rays.iter().all(|&r| r < 0)
581    }
582}
583/// Data tracking the abundance conjecture.
584#[allow(dead_code)]
585#[derive(Debug, Clone)]
586pub struct AbundanceData {
587    /// Variety.
588    pub variety: String,
589    /// Kodaira dimension κ.
590    pub kodaira_dim: Option<i64>,
591    /// Whether K_X is nef.
592    pub kx_nef: bool,
593    /// Whether K_X is semi-ample (abundance holds).
594    pub kx_semi_ample: bool,
595    /// Dimension of the variety.
596    pub dim: usize,
597}
598#[allow(dead_code)]
599impl AbundanceData {
600    /// Creates abundance data.
601    pub fn new(variety: &str, dim: usize) -> Self {
602        AbundanceData {
603            variety: variety.to_string(),
604            kodaira_dim: None,
605            kx_nef: false,
606            kx_semi_ample: false,
607            dim,
608        }
609    }
610    /// Sets Kodaira dimension.
611    pub fn with_kodaira_dim(mut self, kappa: i64) -> Self {
612        self.kodaira_dim = Some(kappa);
613        self
614    }
615    /// Marks K_X as nef.
616    pub fn nef(mut self) -> Self {
617        self.kx_nef = true;
618        self
619    }
620    /// Marks abundance as holding.
621    pub fn abundant(mut self) -> Self {
622        self.kx_semi_ample = true;
623        self
624    }
625    /// Returns conjecture status.
626    pub fn abundance_status(&self) -> String {
627        if self.kx_nef && self.kx_semi_ample {
628            format!(
629                "Abundance holds for {}: K_X nef and semi-ample",
630                self.variety
631            )
632        } else if self.kx_nef {
633            format!("Abundance not yet verified for {}", self.variety)
634        } else {
635            format!("{} does not have nef canonical class", self.variety)
636        }
637    }
638    /// Checks dim-3 abundance (known in dim <= 3).
639    pub fn abundance_known(&self) -> bool {
640        self.dim <= 3
641    }
642}
643/// Minimal model flowchart simulator.
644///
645/// Runs a simplified MMP on a sequence of user-specified steps.
646/// Terminates when a terminal step (minimal model or fiber space) is reached.
647pub struct MmpFlowchart {
648    /// The log pair being processed.
649    pub pair: LogPair,
650    /// The history of steps taken so far.
651    pub history: Vec<MmpStep>,
652    /// The current Picard number (decreases with divisorial contractions).
653    pub picard_number: usize,
654}
655impl MmpFlowchart {
656    /// Create a new MMP flowchart starting from a log pair.
657    pub fn new(pair: LogPair, initial_picard: usize) -> Self {
658        MmpFlowchart {
659            pair,
660            history: Vec::new(),
661            picard_number: initial_picard,
662        }
663    }
664    /// Apply a single MMP step and record it.
665    /// Returns `true` if the MMP terminated, `false` if another step is needed.
666    pub fn apply(&mut self, step: MmpStep) -> bool {
667        let done = step.is_terminal();
668        match &step {
669            MmpStep::Contraction { .. } => {
670                if self.picard_number > 0 {
671                    self.picard_number -= 1;
672                }
673            }
674            MmpStep::Flip { .. } | MmpStep::Flop { .. } => {}
675            MmpStep::MinimalModel | MmpStep::FiberSpace { .. } => {}
676        }
677        self.history.push(step);
678        done
679    }
680    /// Run the MMP on a predetermined sequence of steps.
681    /// Stops at the first terminal step.
682    pub fn run(&mut self, steps: Vec<MmpStep>) -> &[MmpStep] {
683        for step in steps {
684            let done = self.apply(step);
685            if done {
686                break;
687            }
688        }
689        &self.history
690    }
691    /// Summarize the MMP run.
692    pub fn summary(&self) -> String {
693        let mut s = format!(
694            "MMP run on '{}' ({} steps, final Picard number: {}):\n",
695            self.pair.variety,
696            self.history.len(),
697            self.picard_number
698        );
699        for (i, step) in self.history.iter().enumerate() {
700            s.push_str(&format!("  Step {}: {}\n", i + 1, step.description()));
701        }
702        s
703    }
704}
705/// The type of singularity produced after a contraction.
706#[derive(Debug, Clone, PartialEq, Eq)]
707pub enum SingularityType {
708    /// Smooth point.
709    Smooth,
710    /// Terminal singularity: discrepancy > 0 for all exceptional divisors.
711    Terminal,
712    /// Canonical singularity: discrepancy ≥ 0 for all exceptional divisors.
713    Canonical,
714    /// Kawamata log-terminal: discrepancy > -1 for log pairs.
715    Klt,
716    /// Divisorially log-terminal: klt except possibly along reduced boundary.
717    Dlt,
718    /// Log-canonical: discrepancy ≥ -1.
719    LogCanonical,
720}
721impl SingularityType {
722    /// Short name for display.
723    pub fn name(&self) -> &'static str {
724        match self {
725            SingularityType::Smooth => "smooth",
726            SingularityType::Terminal => "terminal",
727            SingularityType::Canonical => "canonical",
728            SingularityType::Klt => "klt",
729            SingularityType::Dlt => "dlt",
730            SingularityType::LogCanonical => "lc",
731        }
732    }
733    /// Partial order: severity (smooth is mildest, lc is most general).
734    /// Returns true if `self` is at least as mild as `other`.
735    pub fn at_least_as_mild_as(&self, other: &SingularityType) -> bool {
736        let rank = |s: &SingularityType| -> usize {
737            match s {
738                SingularityType::Smooth => 0,
739                SingularityType::Terminal => 1,
740                SingularityType::Canonical => 2,
741                SingularityType::Klt => 3,
742                SingularityType::Dlt => 4,
743                SingularityType::LogCanonical => 5,
744            }
745        };
746        rank(self) <= rank(other)
747    }
748}
749/// An extremal ray in the Mori cone NE(X).
750///
751/// Geometrically, this is a half-line R ⊂ NE(X) satisfying
752/// K_X · R < 0 (for the MMP), and any effective curve class
753/// on the ray is a positive multiple of the generator.
754#[derive(Debug, Clone)]
755pub struct ExtremeRay {
756    /// Name or description of the ray generator.
757    pub generator: String,
758    /// Degree K_X · C for a generator C (should be < 0 for K-negative rays).
759    pub k_degree: i64,
760    /// The type of the associated contraction (small, divisorial, or fiber).
761    pub contraction_type: ContractionType,
762}
763impl ExtremeRay {
764    /// Create a new extremal ray.
765    pub fn new(generator: impl Into<String>, k_degree: i64, ty: ContractionType) -> Self {
766        ExtremeRay {
767            generator: generator.into(),
768            k_degree,
769            contraction_type: ty,
770        }
771    }
772    /// Whether the ray is K-negative (required for MMP).
773    pub fn is_k_negative(&self) -> bool {
774        self.k_degree < 0
775    }
776    /// The MMP step corresponding to this ray.
777    pub fn mmp_step(&self) -> MmpStep {
778        match &self.contraction_type {
779            ContractionType::Divisorial => MmpStep::Contraction {
780                divisor: self.generator.clone(),
781                singularity_type: SingularityType::Terminal,
782            },
783            ContractionType::Small => MmpStep::Flip {
784                locus: self.generator.clone(),
785            },
786            ContractionType::FiberType => MmpStep::FiberSpace {
787                base: format!("base({})", self.generator),
788                fiber_dim: 1,
789            },
790        }
791    }
792}