classify — Contraction classification algorithm#

Contraction classification for extremal birational contractions.

Implements the five-type classification algorithm from arXiv:2212.10573 Section 4: asymptotic, CFT, su(2) enhancement, symmetric flop, and generic flop.

Each helper predicate is individually callable (D-06) and uses “classify” terminology throughout (D-11). All docstrings cite the relevant equations from arXiv:2212.10573 (D-12).

classify_contraction(int_nums, c2, curve, gv_series)#

Classify an extremal contraction and return all metadata.

Follows the sequential classification algorithm from arXiv:2212.10573, Section 4:

  1. Check if asymptotic (projected volumes vanish)

  2. Check if CFT (rank-deficient projected matrix)

  3. Compute effective GV invariants

  4. Check potency (raise InsufficientGVError if potent)

  5. Find zero-volume divisor

  6. If no zero-vol divisor: generic flop (Type I)

  7. If zero-vol divisor exists:

    1. Compute Coxeter reflection

    2. Check if symmetric flop

    3. If symmetric and GV signs are non-negative: symmetric flop

    4. If symmetric but negative GV: su(2) enhancement

    5. If not symmetric: generic flop (Type II)

\[\text{asymptotic} \to \text{CFT} \to \text{potent?} \to \text{flop/symmetric/su(2)}\]

See arXiv:2212.10573, Section 4 for the full classification algorithm.

Parameters:
  • int_nums (numpy.ndarray) – Triple intersection numbers, shape (h11, h11, h11).

  • c2 (numpy.ndarray) – Second Chern class, shape (h11,).

  • curve (numpy.ndarray) – Flopping curve class, shape (h11,).

  • gv_series (list[int]) – GV series [GV(C), GV(2C), ...].

Returns:

dict – Keys: contraction_type (ContractionType), zero_vol_divisor (ndarray or None), coxeter_reflection (ndarray or None), gv_invariant (int), effective_gv (int = gv_eff_3), gv_eff_1 (int), gv_series (list[int]).

Raises:

InsufficientGVError – If the GV series appears potent (last entry nonzero).

classify_geometric(int_nums, c2, curve)#

Classify an extremal contraction from geometric data alone (no GVs).

Performs the cheap geometric checks from classify_contraction() – asymptotic, CFT, zero-volume divisor existence, Coxeter-reflection integrality – and returns a PartialClassification capturing what geometry alone determines.

Three branches pin the type down without GVs:

  1. Asymptotic – projected triple intersections vanish.

  2. CFT – projected matrix is rank-deficient.

  3. FLOP – no zero-volume divisor, or a zero-vol divisor exists but the resulting Coxeter reflection has non-integer entries (cannot generate a finite Coxeter group).

When none of these apply, the contraction is one of {SYMMETRIC_FLOP, SU2, GROSS_FLOP, FLOP} and GVs are required to disambiguate (see needs_for_disambiguation on the result).

See arXiv:2212.10573 Section 4 for the underlying classification.

Parameters:
  • int_nums (numpy.ndarray) – Triple intersection numbers \(\kappa_{ijk}\), shape (h11, h11, h11).

  • c2 (numpy.ndarray) – Second Chern class \(c_2 \cdot D_a\), shape (h11,).

  • curve (numpy.ndarray) – Curve class, shape (h11,).

Returns:

PartialClassification – Geometric classification result. Inspect .determined for the unique type when geometry suffices, or .remaining_options when GVs are needed.

Examples

>>> partial = classify_geometric(int_nums, c2, [1, 0, -1])
>>> if partial.determined is not None:
...     print(f"Determined: {partial.determined.name}")
... else:
...     print(f"GVs needed; possibilities: {partial.remaining_options}")
gv_degrees_needed(int_nums, c2, curve)#

Return the minimum GV degree needed to fully classify a curve.

Wraps classify_geometric() and returns 0 when geometry alone determines the contraction type, 3 when GVs are needed. Useful for callers with expensive compute_gvs budgets who want to skip the call entirely when geometry suffices.

Note that 3 is a minimum: in practice you may want a longer series so the trailing GV is zero (the potency-check convergence requirement in classify_contraction()).

Parameters:
  • int_nums (numpy.ndarray) – Triple intersection numbers, shape (h11, h11, h11).

  • c2 (numpy.ndarray) – Second Chern class, shape (h11,).

  • curve (numpy.ndarray) – Curve class, shape (h11,).

Returns:

int0 when geometry alone classifies the curve (ASYMPTOTIC, CFT, or FLOP from no-zvd / non-integer-reflection paths). 3 when GVs are required to disambiguate among {SYMMETRIC_FLOP, SU2, GROSS_FLOP, FLOP}.

Examples

>>> n = gv_degrees_needed(int_nums, c2, curve)
>>> if n == 0:
...     result = diagnose_curve(cy, curve, compute_gvs=False)
... else:
...     gvs = cy.compute_gvs(max_deg=max(n, 10))
...     result = diagnose_curve(cy, curve, gvs=gvs)
is_asymptotic(int_nums, curve)#

Check whether a contraction is asymptotic (Type III).

A contraction is asymptotic when the fully-projected triple intersection numbers vanish:

\[\kappa_{abc} \, \Pi^a_\perp \, \Pi^b_\perp \, \Pi^c_\perp = 0\]

where \(\Pi_\perp\) is the projector onto the complement of the flopping curve \(\mathcal{C}\).

This means the curve direction has no cubic volume contribution transverse to the wall, corresponding to an infinite-distance boundary in moduli space.

See arXiv:2212.10573 Section 2 (asymptotic boundaries).

Parameters:
  • int_nums (numpy.ndarray) – Triple intersection numbers \(\kappa_{ijk}\), shape (h11, h11, h11).

  • curve (numpy.ndarray) – Curve class \(\mathcal{C}_a\), shape (h11,).

Returns:

boolTrue if the contraction is asymptotic.

is_cft(int_nums, curve)#

Check whether a contraction is a CFT boundary (Type II).

A contraction is a CFT boundary when the projected intersection-number matrix (one index projected) is rank-deficient:

\[\operatorname{rank}\bigl( \kappa_{ajk} \, \Pi^a_\perp \bigr) < h^{1,1} - 1\]

This signals that some divisor volumes remain non-vanishing at the wall, corresponding to a CFT point in moduli space.

See arXiv:2212.10573 Section 2 (CFT boundaries).

Parameters:
  • int_nums (numpy.ndarray) – Triple intersection numbers \(\kappa_{ijk}\), shape (h11, h11, h11).

  • curve (numpy.ndarray) – Curve class \(\mathcal{C}_a\), shape (h11,).

Returns:

boolTrue if the contraction is a CFT boundary.

is_symmetric_flop(int_nums, c2, curve, gv_eff_1, gv_eff_3, coxeter_reflection, source_kc=None, flopped_kc=None)#

Check whether a flop is symmetric under the Coxeter reflection.

A flop is symmetric when two conditions hold:

(a) The wall-crossed intersection numbers and second Chern class equal their Coxeter-reflected counterparts:

\[\kappa'_{abc} = M_{ai} M_{bj} M_{ck} \, \kappa_{ijk} \quad\text{and}\quad c'_{2,a} = M^T_{ai} \, c_{2,i}\]

(b) The Coxeter reflection maps the source Kahler cone to the flopped Kahler cone.

If condition (a) passes but (b) fails, the flop is a gross flop (condition (a) satisfied at the level of intersection numbers, but the Kahler cones do not match under the reflection).

See arXiv:2212.10573 Section 4 (symmetric flops).

Parameters:
  • int_nums (numpy.ndarray) – Triple intersection numbers \(\kappa_{ijk}\).

  • c2 (numpy.ndarray) – Second Chern class \(c_2 \cdot D_a\).

  • curve (numpy.ndarray) – Flopping curve class.

  • gv_eff_1 (int) – Linear effective GV invariant.

  • gv_eff_3 (int) – Cubic effective GV invariant.

  • coxeter_reflection (numpy.ndarray) – Coxeter reflection matrix \(M_{ab}\).

  • source_kc (optional) – Source phase Kahler cone (cytools.Cone). If None, condition (b) is skipped for backward compatibility.

  • flopped_kc (optional) – Flopped phase Kahler cone (cytools.Cone). If None, condition (b) is skipped for backward compatibility.

Returns:

tuple[bool, bool](is_symmetric, is_gross_flop). If condition (a) fails, returns (False, False). If (a) passes and (b) fails, returns (False, True) (gross flop). If both pass (or cones not provided), returns (True, False).

zero_vol_divisor(int_nums, curve)#

Find a divisor that shrinks to zero volume at this wall.

Computes the null space of the projected intersection matrix contracted with the flopping curve:

\[M_{\alpha\beta} = \kappa_{ijk} \, \Pi^i_\alpha \, \Pi^j_\beta \, \mathcal{C}^k\]

where \(\Pi\) is the projection onto the complement of \(\mathcal{C}\). If \(M\) has a null vector \(v_\alpha\), the zero-volume divisor in the full basis is \(D_i = \Pi^T_{i\alpha} v_\alpha\), cleaned to integer form.

Sign convention: \(D \cdot \mathcal{C} < 0\).

The zero-volume divisor is defined only up to sign (it spans a 1D null space). The sign is fixed by requiring \(D \cdot C < 0\), where \(C\) is the inward-pointing Mori cone generator of the source phase. Physically, this is the divisor that would become effective if one tuned complex structure to make this wall an su(2) enhancement, taking the source phase as the fundamental domain.

Under the Coxeter reflection \(R\) associated to the wall, \(R^{-T} D = -D\) (the reflection acting on divisors sends \(D \to -D\)). This is consistent: viewing the wall from the other phase reverses which side is the fundamental domain and requires \(-D\) as the would-be effective divisor. Thus the same wall produces opposite-sign zvds when classified from opposite phases — this is expected, not a bug.

See arXiv:2212.10573 Section 4 (shrinking divisors).

Parameters:
  • int_nums (numpy.ndarray) – Triple intersection numbers \(\kappa_{ijk}\), shape (h11, h11, h11).

  • curve (numpy.ndarray) – Curve class \(\mathcal{C}_a\), shape (h11,).

Returns:

numpy.ndarray or None – Integer divisor class with \(D \cdot \mathcal{C} < 0\), or None if no zero-volume divisor exists.