Metadata-Version: 2.4
Name: schubmult
Version: 5.4.0
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 :: Python :: 3.15
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/>

**Citation:** [How to cite](#how-to-cite)

---

## 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, QDGx, 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

# Quantum double Grothendieck polynomials (conjectural equivariant quantum K-Pieri rule)
g = QDGx([1, 3, 2], beta=-1)
h = QDGx([2, 1, 3], beta=-1)
product = (g * h).simplify()                 # multiplication uses the combinatorial kernel
product.expand()                           # explicit Lenart--Maeno quantum polynomials
g * QDGx([2, 1, 3], "z", beta=-1)           # mixed coefficient alphabets

# 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`).

`QDGx` constructs stable quantum double Grothendieck elements, with symbolic beta by default.
It supports linear combinations, kernel multiplication, coefficient simplification, and explicit
polynomial expansion; `QDGx(w, "0")` uses the zero coefficient alphabet. The ring class is
`QuantumDoubleGrothendieckRing(x_alphabet, y_alphabet, beta=None)`. Arbitrary x-polynomial
conversion and coproducts are not implemented. Products retain permutations outside any chosen
finite symmetric group; a finite-flag comparison must handle those separately.

### Paper examples: equivariant quantum K-theory

[examples/duan_khlaif_zou_qk.py](examples/duan_khlaif_zou_qk.py) computes all 156 distinct
explicit equivariant Schubert-class products in [Duan--Khlaif--Zou, arXiv:2606.15261v2](https://arxiv.org/abs/2606.15261v2):
Table 3.2 (Fl(3)), Appendix A.1 (Fl(4)), Appendix A.2 (Fl(1,3;4)), and Appendix A.3 (Fl(1,2;4)).
The script records printed page references and notes the repeated product in A.2 and the
omitted equivariant square in A.3. It generates new answers, rather than automatically comparing
against the paper's printed right-hand sides.

```bash
PYTHONPATH=src python examples/duan_khlaif_zou_qk.py --list
PYTHONPATH=src python examples/duan_khlaif_zou_qk.py --example fl4-04
PYTHONPATH=src python examples/duan_khlaif_zou_qk.py --section fl134
PYTHONPATH=src python examples/duan_khlaif_zou_qk.py --tex --output products.tex
```

It uses `QuantumDoubleGrothendieckRing` at `beta=-1` with `ChangeOfVariables` implementing
`z_i -> 1-1/y_i`, the paper's multiplicative equivariant-character convention. Finite-flag
projection removes stable terms outside `S_n`; partial flags additionally use the existing
Kato projection, with consecutive quantum-parameter indices. TeX output is a standalone
document using `\widetilde{\mathfrak{G}}_w(x;y)`. Add `--non-equivariant` to specialize the
selected products at `y_i=1`. The underlying equivariant quantum K-Pieri multiplication
rule remains conjectural.

### Generating sets

`GeneratingSet("x")` supports any nonnegative index, including indices above 100.
It starts with 10 cached symbols and grows geometrically on demand. Bounded slices
such as `x[1:151]` also generate the requested variables. `len(x)`, iteration,
negative indexing, and open-ended slices describe only the currently cached prefix;
use explicit slice bounds when a computation needs a particular number of variables.
`CustomGeneratingSet` remains a finite sequence.

```python
from schubmult import ChangeOfVariables, GeneratingSet

x, t = GeneratingSet("x"), GeneratingSet("t")
change = ChangeOfVariables(x, t, lambda i, target: 1 - 1 / target[i])
change[150]                     # 1 - 1/t_150
change.subs(x[150] + x[1]**2)   # simultaneous substitution of source variables
```

### 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 β)
grothmult_q_double 2 1 - 2 1 --multiplicative-variables --simplify  # beta=-1, coordinates 1-1/y_i
```

Both `grothmult_double` and `grothmult_q_double` support
`--multiplicative-variables`: this uses `ChangeOfVariables` to calculate with
`1-1/y_i` coefficient coordinates and fixes `beta=-1`. With `--mixed-var`, the
second coefficient alphabet is also changed to `1-1/z_i`. Explicit
`--beta=-1` is allowed; other beta values and `--display-positive` conflict
with this mode and are rejected. Add `--simplify` to cancel the rational
coefficients. Without the flag, the existing conventions are unchanged.

`--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.

All four `grothmult_*` scripts accept `--beta EXPR` to use a sympified expression as beta in the multiplication, for example `--beta=-1`, `--beta 0`, or `--beta "1/2"`. Without this option, beta remains symbolic.
For `--display-positive`, the positive expression is constructed with symbolic beta before specialization. With `--parabolic --beta 0`, specialization is deferred until after Kato projection so inverse powers of beta can cancel.

## 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`** -- K-theoretic pipe dream models.
- **Tableaux** -- `Plactic` (semistandard, jeu de taquin), `NilPlactic` (Edelman-Greene), `IncreasingTableau`, and `SetValuedTableau`.
- **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
```

### Experimental combinatorics

The `schubmult.combinatorics.experimental` namespace contains exploratory
implementations that may be incomplete or incorrect:

| Module | Main objects |
| --- | --- |
| `chute_move_element` | `ChuteMoveElement` |
| `hecke_plactic` | `HeckePlactic` |
| `hpd` | `HPD`, `HPDTile` |
| `inversions_tableau` | `InversionsTableau` |
| `quasi_crystal_graph` | `QuasiCrystalGraph`, `QuasiCrystalGraphTensor` |
| `root_tableau` | `RootTableau` |

These objects can be imported from the experimental namespace or their
individual modules:

```python
from schubmult.combinatorics.experimental import HPD
from schubmult.combinatorics.experimental.root_tableau import RootTableau
```

The former `schubmult.combinatorics.<module>` paths and top-level
`schubmult` exports for these objects are no longer provided.
Pickles referencing the former module paths will not load without migration.
`ChuteMoveRing` remains available, but its basis uses `ChuteMoveElement`.

## 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.

## How to cite

If you use `schubmult` in your research, please cite the software and, once available,
the accompanying preprint. Include the version number (or Git commit hash for a
development checkout) used for your computations.

On GitHub, the **Cite this repository** button in the repository sidebar gives
APA and BibTeX citations generated from [CITATION.cff](CITATION.cff).

### Software

Use this BibTeX template, replacing `<YEAR>` with the year of the cited release or
commit and `<VERSION_OR_COMMIT>` with the version number or commit hash you used:

```bibtex
@misc{schubmult,
  author = {Samuel, Matt},
  title  = {{schubmult}},
  year   = {<YEAR>},
  url    = {https://github.com/matthematics/schubmult},
  note   = {Version <VERSION_OR_COMMIT>}
}
```

### Preprint (template)

**arXiv link:** `<ARXIV_URL>`

The preprint citation is not yet filled in. Replace the placeholders below with
the paper's authors, title, year, arXiv identifier, and URL before citing it, or
paste the BibTeX entry exported by arXiv. Also fill in and uncomment the
`preferred-citation` block in [CITATION.cff](CITATION.cff). The software citation
above can be used in the meantime.

```bibtex
@misc{schubmult_paper,
  author        = {<AUTHORS>},
  title         = {<PAPER_TITLE>},
  year          = {<YEAR>},
  eprint        = {<ARXIV_ID>},
  archivePrefix = {arXiv},
  url           = {<ARXIV_URL>}
}
```

## License

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