Metadata-Version: 2.4
Name: schubmult
Version: 5.2.0b2
Summary: Package for algebraic computation with single, double, and quantum Schubert polynomials
Author-email: Matt Samuel <schubmult@gmail.com>
Maintainer-email: Matt Samuel <schubmult@gmail.com>
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, http://schubmult.org
Project-URL: Repository, https://github.com/matthematics/schubmult
Keywords: Schubert,polynomial,double,algebra
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: C++
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: joblib
Requires-Dist: latex2sympy2_extended
Requires-Dist: PuLP[cbc]<4,>=3.3.2
Requires-Dist: symengine>=0.14.1
Requires-Dist: sympy>=1.14.0
Requires-Dist: psutil
Requires-Dist: cachetools
Requires-Dist: setuptools
Provides-Extra: visualization
Requires-Dist: matplotlib>=3.0; extra == "visualization"
Provides-Extra: docs
Requires-Dist: pydoc-markdown>=4.8; extra == "docs"
Requires-Dist: mkdocs>=1.6; extra == "docs"
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-xdist; extra == "test"
Requires-Dist: mypy; extra == "test"
Dynamic: license-file

# schubmult

**Fast Schubert calculus in Python: Littlewood-Richardson coefficients, Schubert/Grothendieck rings, and the combinatorics behind them.**

[![PyPI](https://img.shields.io/pypi/v/schubmult)](https://pypi.org/project/schubmult/)
[![Docs](https://img.shields.io/badge/docs-matthematics.github.io%2Fschubmult-blue)](https://matthematics.github.io/schubmult/)
[![License: GPL v3](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE)

`schubmult` multiplies single, double, quantum, and quantum double Schubert polynomials (with parabolic variants), expands products in the Schubert basis, and gives you the combinatorial objects that index them: permutations, RC graphs (pipe dreams), bumpless pipe dreams, tableaux, and their crystal structures. It is built on [SymEngine](https://github.com/symengine/symengine.py) for speed and [SymPy](https://www.sympy.org/) for display, so results drop straight into SymPy or Sage.

**Documentation:** <https://matthematics.github.io/schubmult/>

---

## Installation

```bash
pip install schubmult
```

Development version:

```bash
pip install git+https://github.com/matthematics/schubmult.git
```

Requires Python 3.10+. To build the documentation locally, install the `docs` extra: `pip install -e ".[docs]"`.

## Quick start

### Python

```python
from schubmult import Sx, DSx, QSx, QDSx, Gx, DGx, Permutation
from schubmult.abc import x, y, z

# Ordinary Schubert polynomials: S_312 * S_132 = S_321 + S_4123
Sx([3, 1, 2]) * Sx([1, 3, 2])
# 𝔖_(3, 2, 1)(x) + 𝔖_(4, 1, 2, 3)(x)

# Expand to a polynomial, or go the other way
Sx([3, 1, 2]).expand()                       # x_1**2
Sx.from_expr(x[1]**2 + x[1] * x[2])          # 𝔖_(2, 3, 1)(x) + 𝔖_(3, 1, 2)(x)

# Double Schubert polynomials S_w(x; y); coefficients are polynomials in y
DSx([1, 3, 2]) * DSx([1, 3, 2])
# (-y_2 + y_3)*𝔖_(1, 3, 2)(x; y) + 𝔖_(1, 4, 2, 3)(x; y) + 𝔖_(2, 3, 1)(x; y)

# Mixed variables: S_w(x; y) * S_v(x; z)
DSx([1, 3, 2]) * DSx([2, 1, 3], z)
# (y_1 - z_1)*𝔖_(1, 3, 2)(x; y) + 𝔖_(2, 3, 1)(x; y) + 𝔖_(3, 1, 2)(x; y)

# Quantum Schubert polynomials
QSx([2, 1, 3]) * QSx([2, 1, 3])              # q_1 + 𝕼𝔖_312(x)

# Quantum double Schubert polynomials
QDSx([2, 1, 3]) * QDSx([2, 1, 3])
# q_1 + (-y_1 + y_2)*𝕼𝔖_21(x; y) + 𝕼𝔖_312(x; y)

# Grothendieck polynomials (K-theory)
Gx([2, 1, 3]) * Gx([2, 1, 3])                # 𝔊_(3, 1, 2)(x)

# Double Grothendieck polynomials; .simplify() puts the rational coefficients in y, β in normal form
(DGx([1, 3, 2]) * DGx([2, 1, 3])).simplify()
# β*𝔊_(3, 2, 1)(x; y) + 𝔊_(2, 3, 1)(x; y) + 𝔊_(3, 1, 2)(x; y)
(DGx([2, 1, 3]) * DGx([2, 1, 3])).simplify()
# (y_1 - y_2)*𝔊_(2, 1)(x; y)   (y_1*β + 1)*𝔊_(3, 1, 2)(x; y)
# -------------------------- + -----------------------------
#         y_2*β + 1                      y_2*β + 1

# Permutations use one-line notation; Lehmer codes are available too
w = Permutation([3, 1, 4, 2])
w.code, w.inv, w.descents()                  # ([2, 0, 1], 3, {0, 2})
```

Ring objects: `Sx` (single), `DSx` (double), `QSx`/`QDSx` (quantum, quantum double), `QPSx`/`QPDSx` (parabolic quantum), `Gx`/`DGx` (single/double Grothendieck). Elements are dictionaries `{Permutation: coefficient}` with `*`, `+`, `.expand()`, `.coproduct()`, and change of basis between them. The underlying multiplication kernels live in `schubmult.mult` (`single`, `double`, `quantum`, `quantum_double`, `groth`, `groth_double`).

### Command line

Each ring has a CLI. Permutations are space-separated, factors are separated by `-`:

```bash
schubmult_py 3 1 2 - 2 1 3                         # 1  (4, 1, 2, 3)
schubmult_py --code 2 0 - 1 0                      # same product via Lehmer codes
schubmult_double 1 3 2 - 1 3 2 --display-positive  # double Schubert, coefficients displayed positively
schubmult_q 2 1 3 - 2 1 3                          # quantum
schubmult_q_double 2 1 3 - 2 1 3 --parabolic 1     # parabolic quantum double
grothmult_py 2 1 3 - 2 1 3                         # Grothendieck
grothmult_double 2 1 3 - 2 1 3                     # double Grothendieck (coefficients in y and β)
grothmult_q 2 1 - 2 1                              # quantum Grothendieck (conjectural quantum K-Pieri rule; coefficients in q and β)
grothmult_q_double 2 1 - 2 1 --mixed-var           # quantum double Grothendieck (coefficients in y, z, q and β)
```

`--display-positive` writes double and quantum double coefficients as manifestly positive expressions in the differences `y_i - z_j` (Graham positivity), using integer programming to find a positive representative. Run any script with `--help` for the full option list.

## Combinatorics

The `schubmult.combinatorics` package provides the objects that index Schubert calculus, with conversions between them:

- **`Permutation`** -- one-line notation, Lehmer codes (`uncode`), reduced words, Bruhat order, descents, dominant/Grassmannian tests.
- **`RCGraph`** -- reduced compatible sequences / pipe dreams, with enumeration (`RCGraph.all_rc_graphs(w, n)`), Kashiwara crystal operators (`raising_operator(i)`, `lowering_operator(i)`), Edelman-Greene insertion, and the crystal products used in the transition formulas.
- **`BPD`** -- bumpless pipe dreams with droop moves, the Gao-Huang bijection to RC graphs (`BPD.from_rc_graph`, `.to_rc_graph()`), and marked/unreduced variants for Grothendieck polynomials.
- **`WCGraph`**, **`PipeDream`**, **`HPD`** -- K-theoretic and hybrid pipe dream models.
- **Tableaux** -- `Plactic` (semistandard, jeu de taquin), `NilPlactic` (Edelman-Greene), `RootTableau`, `IncreasingTableau`, `SetValuedTableau`, `HeckePlactic`.
- **Forests** -- indexed forests and the Thompson monoid factorization behind the forest and grove bases.

```python
from schubmult import RCGraph, BPD, Permutation

w = Permutation([1, 4, 2, 3])
rcs = RCGraph.all_rc_graphs(w, 3)        # all RC graphs of w in 3 rows

rc = next(rc for rc in rcs if rc.lowering_operator(1) is not None)
rc.lowering_operator(1)                  # crystal operator f_1 (None when undefined)

bpd = BPD.from_rc_graph(rc)              # Gao-Huang bijection
assert bpd.to_rc_graph() == rc
```

## Rings and algebras

Beyond the Schubert rings, `schubmult.rings` includes:

- **`PolynomialAlgebra`** with interchangeable bases: monomials, Schubert, key (Demazure), Lascoux, fundamental/monomial slide, glide, forest, grove, Grothendieck, and elementary symmetric bases.
- **`FreeAlgebra`** -- the graded dual of the polynomial algebra (a word `(a_1, ..., a_n)` is dual to `x_1^a_1 ... x_n^a_n`), with a dual basis for each of the above; `ASx` is the dual Schubert basis.
- **Combinatorial rings** -- `RCGraphRing`, `WCGraphRing`, `CrystalGraphRing`, and related algebras whose basis elements are the combinatorial objects themselves.
- **Tensor and direct products**, `NSym`, `QSym`, and the nil-Hecke algebra.

```python
from schubmult import ASx, FA

ASx([2, 1, 3]) * ASx([1, 3, 2])   # dual Schubert basis of the free algebra
FA(1, 0) * FA(2)                  # word basis: concatenation, [102]
```

## Symbolic layer

`schubmult.symbolic` wraps SymEngine and SymPy behind one interface (`sympify`, `expand`, `Add`, `Mul`, `S`), provides indexed variable families (`GeneratingSet("x")` gives `x_1, x_2, ...`), and unevaluated elementary/complete symmetric polynomial atoms (`E`, `e`, `H`, `h`) so Schubert polynomials can be manipulated in the SEM basis. `schubmult.abc` exposes ready-made `x`, `y`, `z`, `q`, `beta` in the spirit of `sympy.abc`.

## Using with SageMath

Sage is **not** a dependency: everything above works in plain Python. If you do have Sage, install schubmult into that environment (`sage -pip install schubmult`, or `pip install schubmult` inside a conda `sage` env) and `schubmult.sage` provides Sage parents whose arithmetic is delegated to the kernels, alongside Sage's own `SchubertPolynomialRing`:

```python
sage: from schubmult.sage import DoubleSchubertPolynomialRing, QuantumSchubertPolynomialRing
sage: X = DoubleSchubertPolynomialRing(QQ)
sage: X([3, 1, 2]) * X([2, 1])
(y_2-y_0)*X_y[3, 1, 2] + X_y[4, 1, 2, 3]
sage: X([3, 1, 2]).expand()
x0^2 - x0*y0 - x0*y1 + y0*y1
sage: G = QuantumSchubertPolynomialRing(QQ, parabolic=(2, 3))   # QH^*(Gr(2, 5))
sage: G([3, 5, 1, 2, 4]) * G([1, 3, 2])
q_0*Xq[1, 3, 2] + Xq[3, 6, 1, 2, 4, 5] + Xq[4, 5, 1, 2, 3]
sage: from schubmult.sage import DoubleGrothendieckPolynomialRing
sage: GD = DoubleGrothendieckPolynomialRing(QQ)                  # K_T of the flag variety
sage: GD([2, 1]) * GD([2, 1])
-((y_1-y_0)/(beta*y_1+1))*G_y[2, 1] + ((beta*y_0+1)/(beta*y_1+1))*G_y[3, 1, 2]
sage: from schubmult.sage import PolynomialAlgebra
sage: A = PolynomialAlgebra(QQ); k = A.key(); F = A.fundamental_slide()   # bases of QQ[x0, x1, ...]
sage: F(k[1, 0, 2])
F[1, 0, 2] + F[2, 0, 1]
sage: k(A.schubert()[3, 1, 2])
k[2]
```

`DoubleSchubertPolynomialRing`, `QuantumSchubertPolynomialRing`, `QuantumDoubleSchubertPolynomialRing` (with `parabolic=` block sizes), `GrothendieckPolynomialRing`, and `DoubleGrothendieckPolynomialRing` accept permutations, Sage polynomials, and elements of `SchubertPolynomialRing` and `KeyPolynomials`; a ring in another alphabet (`DoubleSchubertPolynomialRing(QQ, 'z')`) coerces in for mixed products, and `from_symmetric_function(f, n)` / `to_symmetric_function()` go back and forth with `SymmetricFunctions`. `PolynomialAlgebra(R)` (or `PolynomialAlgebra(R, n)` in `n` variables) is the polynomial ring with its combinatorial bases as mutually coercing realizations: Schubert and Grothendieck indexed by permutations, monomial/key/slide/forest/glide/Lascoux/grove by weak compositions, and in `n` variables the elementary symmetric basis; its K-theoretic bases are at `beta = -1`, whereas the Grothendieck rings keep `beta` as a variable. Variables are 0-indexed as in Sage (`x0, y0, q0`).

## Documentation

Full API documentation, generated from the source docstrings, is at **<https://matthematics.github.io/schubmult/>**. To build it locally:

```bash
pip install -e ".[docs]"
python docs/generate_docs.py
mkdocs serve
```

## Development

```bash
git clone https://github.com/matthematics/schubmult.git
cd schubmult
pip install -e .
pytest
```

Tests live in `tests/`; the script tests compare CLI output against stored JSON cases in `tests/scripts/data`. `ruff check src/schubmult` should pass.

## License

GPL-3.0-or-later. See [LICENSE](LICENSE).
