schubmult 5.4.0__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schubmult/__init__.py +232 -0
- schubmult/_scripts/__init__.py +7 -0
- schubmult/_scripts/grothmult_double.py +396 -0
- schubmult/_scripts/grothmult_py.py +83 -0
- schubmult/_scripts/grothmult_q.py +118 -0
- schubmult/_scripts/grothmult_q_double.py +121 -0
- schubmult/_scripts/schubmult_double.py +412 -0
- schubmult/_scripts/schubmult_py.py +101 -0
- schubmult/_scripts/schubmult_q.py +131 -0
- schubmult/_scripts/schubmult_q_double.py +229 -0
- schubmult/_typing.py +46 -0
- schubmult/_version.py +24 -0
- schubmult/abc.py +69 -0
- schubmult/combinatorics/__init__.py +62 -0
- schubmult/combinatorics/anti_rc_graph.py +373 -0
- schubmult/combinatorics/bpd.py +2119 -0
- schubmult/combinatorics/crystal_graph.py +588 -0
- schubmult/combinatorics/double_forest.py +541 -0
- schubmult/combinatorics/experimental/__init__.py +32 -0
- schubmult/combinatorics/experimental/chute_move_element.py +145 -0
- schubmult/combinatorics/experimental/hecke_plactic.py +255 -0
- schubmult/combinatorics/experimental/hpd.py +2787 -0
- schubmult/combinatorics/experimental/inversions_tableau.py +418 -0
- schubmult/combinatorics/experimental/quasi_crystal_graph.py +210 -0
- schubmult/combinatorics/experimental/root_tableau.py +1234 -0
- schubmult/combinatorics/increasing_tableau.py +637 -0
- schubmult/combinatorics/indexed_forests.py +1638 -0
- schubmult/combinatorics/mbpd.py +941 -0
- schubmult/combinatorics/nilplactic.py +523 -0
- schubmult/combinatorics/permutation.py +1111 -0
- schubmult/combinatorics/pipe_dream.py +170 -0
- schubmult/combinatorics/plactic.py +1159 -0
- schubmult/combinatorics/planar_history.py +106 -0
- schubmult/combinatorics/rc_graph.py +2863 -0
- schubmult/combinatorics/schub_poly.py +4 -0
- schubmult/combinatorics/schubert_monomial_graph.py +152 -0
- schubmult/combinatorics/set_valued_tableau.py +214 -0
- schubmult/combinatorics/set_word.py +99 -0
- schubmult/combinatorics/wc_graph.py +1508 -0
- schubmult/mult/__init__.py +131 -0
- schubmult/mult/_accel.py +112 -0
- schubmult/mult/_shadow.py +78 -0
- schubmult/mult/double.py +760 -0
- schubmult/mult/groth.py +246 -0
- schubmult/mult/groth_double.py +1479 -0
- schubmult/mult/groth_quantum.py +130 -0
- schubmult/mult/groth_quantum_double.py +581 -0
- schubmult/mult/positivity.py +607 -0
- schubmult/mult/quantum.py +290 -0
- schubmult/mult/quantum_double.py +816 -0
- schubmult/mult/separated_descents.py +390 -0
- schubmult/mult/single.py +326 -0
- schubmult/mult/transition.py +164 -0
- schubmult/rings/__init__.py +94 -0
- schubmult/rings/base_ring.py +379 -0
- schubmult/rings/combinatorial/__init__.py +54 -0
- schubmult/rings/combinatorial/alt_rc_graph_ring.py +589 -0
- schubmult/rings/combinatorial/bounded_rc_factor_algebra.py +777 -0
- schubmult/rings/combinatorial/bounded_rc_forest_factor_algebra.py +1016 -0
- schubmult/rings/combinatorial/bounded_wc_factor_algebra.py +810 -0
- schubmult/rings/combinatorial/bpd_ring.py +82 -0
- schubmult/rings/combinatorial/chute_move_ring.py +46 -0
- schubmult/rings/combinatorial/crystal_graph_ring.py +164 -0
- schubmult/rings/combinatorial/dual_rc_graph_ring.py +232 -0
- schubmult/rings/combinatorial/eg_plactic_ring.py +270 -0
- schubmult/rings/combinatorial/eg_ring.py +117 -0
- schubmult/rings/combinatorial/forest_invariant_rc_ring.py +49 -0
- schubmult/rings/combinatorial/forest_rc_ring.py +208 -0
- schubmult/rings/combinatorial/grass_tensor_algebra.py +346 -0
- schubmult/rings/combinatorial/grove_wc_ring.py +324 -0
- schubmult/rings/combinatorial/hw_rc_ring.py +76 -0
- schubmult/rings/combinatorial/key_rc_ring.py +89 -0
- schubmult/rings/combinatorial/plactic_algebra.py +167 -0
- schubmult/rings/combinatorial/qy_rc_graph_ring.py +66 -0
- schubmult/rings/combinatorial/rc_graph_ring.py +921 -0
- schubmult/rings/combinatorial/rc_schubert_ring.py +1043 -0
- schubmult/rings/combinatorial/schubert_monomial_ring.py +130 -0
- schubmult/rings/combinatorial/schubert_rc_ring.py +96 -0
- schubmult/rings/combinatorial/slide_rc_ring.py +99 -0
- schubmult/rings/combinatorial/wc_graph_ring.py +211 -0
- schubmult/rings/direct_product_ring.py +211 -0
- schubmult/rings/free_algebra/__init__.py +101 -0
- schubmult/rings/free_algebra/_core.py +723 -0
- schubmult/rings/free_algebra/composition_schubert_basis.py +120 -0
- schubmult/rings/free_algebra/elementary_basis.py +177 -0
- schubmult/rings/free_algebra/forest_basis.py +84 -0
- schubmult/rings/free_algebra/free_algebra_basis.py +265 -0
- schubmult/rings/free_algebra/fundamental_slide_basis.py +108 -0
- schubmult/rings/free_algebra/glide_basis.py +74 -0
- schubmult/rings/free_algebra/grothendieck_basis.py +197 -0
- schubmult/rings/free_algebra/grove_basis.py +83 -0
- schubmult/rings/free_algebra/j_basis.py +79 -0
- schubmult/rings/free_algebra/jt_basis.py +91 -0
- schubmult/rings/free_algebra/key_basis.py +107 -0
- schubmult/rings/free_algebra/lascoux_basis.py +102 -0
- schubmult/rings/free_algebra/monomial_slide_basis.py +159 -0
- schubmult/rings/free_algebra/nelementary_basis.py +57 -0
- schubmult/rings/free_algebra/schubert_basis.py +375 -0
- schubmult/rings/free_algebra/schubert_schur_basis.py +92 -0
- schubmult/rings/free_algebra/schur_elementary_basis.py +92 -0
- schubmult/rings/free_algebra/separated_descents_basis.py +110 -0
- schubmult/rings/free_algebra/word_basis.py +574 -0
- schubmult/rings/free_algebra/z_basis.py +101 -0
- schubmult/rings/nsym.py +252 -0
- schubmult/rings/polynomial_algebra/__init__.py +70 -0
- schubmult/rings/polynomial_algebra/_core.py +238 -0
- schubmult/rings/polynomial_algebra/anti_schubert_poly_basis.py +288 -0
- schubmult/rings/polynomial_algebra/base_polynomial_basis.py +140 -0
- schubmult/rings/polynomial_algebra/composition_schubert_poly_basis.py +82 -0
- schubmult/rings/polynomial_algebra/double_forest_poly_basis.py +144 -0
- schubmult/rings/polynomial_algebra/elem_sym_poly_basis.py +99 -0
- schubmult/rings/polynomial_algebra/forest_poly_basis.py +231 -0
- schubmult/rings/polynomial_algebra/fundamental_slide_poly_basis.py +260 -0
- schubmult/rings/polynomial_algebra/glide_poly_basis.py +185 -0
- schubmult/rings/polynomial_algebra/grothendieck_poly_basis.py +200 -0
- schubmult/rings/polynomial_algebra/grove_poly_basis.py +125 -0
- schubmult/rings/polynomial_algebra/key_poly_basis.py +109 -0
- schubmult/rings/polynomial_algebra/lascoux_poly_basis.py +128 -0
- schubmult/rings/polynomial_algebra/monomial_basis.py +182 -0
- schubmult/rings/polynomial_algebra/monomial_slide_poly_basis.py +85 -0
- schubmult/rings/polynomial_algebra/polynomial_basis.py +23 -0
- schubmult/rings/polynomial_algebra/schubert_poly_basis.py +293 -0
- schubmult/rings/polynomial_algebra/sepdesc_poly_basis.py +99 -0
- schubmult/rings/printing.py +551 -0
- schubmult/rings/product_ring.py +207 -0
- schubmult/rings/quasisymmetric_functions.py +191 -0
- schubmult/rings/schubert/__init__.py +107 -0
- schubmult/rings/schubert/base_schubert_ring.py +271 -0
- schubmult/rings/schubert/beta_coxeter.py +405 -0
- schubmult/rings/schubert/chevalley.py +110 -0
- schubmult/rings/schubert/double_grothendieck_ring.py +403 -0
- schubmult/rings/schubert/double_schubert_ring.py +947 -0
- schubmult/rings/schubert/grothendieck_ring.py +169 -0
- schubmult/rings/schubert/nil_hecke.py +429 -0
- schubmult/rings/schubert/parabolic_quantum_double_schubert_ring.py +415 -0
- schubmult/rings/schubert/parabolic_quantum_schubert_ring.py +135 -0
- schubmult/rings/schubert/quantum_double_grothendieck_ring.py +173 -0
- schubmult/rings/schubert/quantum_double_schubert_ring.py +323 -0
- schubmult/rings/schubert/quantum_schubert_ring.py +136 -0
- schubmult/rings/schubert/schubert_ring.py +117 -0
- schubmult/rings/schubert/separated_descents.py +328 -0
- schubmult/rings/tensor_ring.py +324 -0
- schubmult/rings/thompson_algebra.py +111 -0
- schubmult/sage/__init__.py +34 -0
- schubmult/sage/_common.py +451 -0
- schubmult/sage/_convert.py +271 -0
- schubmult/sage/double_schubert.py +332 -0
- schubmult/sage/grothendieck.py +342 -0
- schubmult/sage/polynomial_algebra.py +788 -0
- schubmult/sage/quantum_schubert.py +359 -0
- schubmult/sage/symengine_ring.py +508 -0
- schubmult/schubmult_cpp.cp315-win_amd64.pyd +0 -0
- schubmult/symbolic/__init__.py +64 -0
- schubmult/symbolic/common_polys.py +8 -0
- schubmult/symbolic/domain.py +59 -0
- schubmult/symbolic/functions.py +118 -0
- schubmult/symbolic/poly/__init__.py +5 -0
- schubmult/symbolic/poly/schub_poly.py +1384 -0
- schubmult/symbolic/poly/variables.py +555 -0
- schubmult/symbolic/symmetric_polynomials/__init__.py +14 -0
- schubmult/symbolic/symmetric_polynomials/complete_sym.py +274 -0
- schubmult/symbolic/symmetric_polynomials/elem_sym.py +410 -0
- schubmult/symbolic/symmetric_polynomials/functions.py +229 -0
- schubmult/symbolic/symmetric_polynomials/qelem_sym.py +141 -0
- schubmult/utils/__init__.py +3 -0
- schubmult/utils/_grid_print.py +95 -0
- schubmult/utils/_lazy.py +46 -0
- schubmult/utils/_mul_utils.py +51 -0
- schubmult/utils/_printable.py +33 -0
- schubmult/utils/argparse.py +304 -0
- schubmult/utils/logging.py +16 -0
- schubmult/utils/parsing.py +23 -0
- schubmult/utils/perm_utils.py +467 -0
- schubmult/utils/schub_lib.py +1178 -0
- schubmult/utils/test_utils.py +111 -0
- schubmult/utils/tuple_utils.py +6 -0
- schubmult/visualization.py +430 -0
- schubmult-5.4.0.dist-info/DELVEWHEEL +2 -0
- schubmult-5.4.0.dist-info/METADATA +377 -0
- schubmult-5.4.0.dist-info/RECORD +185 -0
- schubmult-5.4.0.dist-info/WHEEL +5 -0
- schubmult-5.4.0.dist-info/entry_points.txt +9 -0
- schubmult-5.4.0.dist-info/licenses/LICENSE +674 -0
- schubmult-5.4.0.dist-info/top_level.txt +1 -0
- schubmult.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
schubmult/__init__.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# start delvewheel patch
|
|
5
|
+
def _delvewheel_patch_1_13_2():
|
|
6
|
+
import os
|
|
7
|
+
if os.path.isdir(libs_dir := os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'schubmult.libs'))):
|
|
8
|
+
os.add_dll_directory(libs_dir)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_delvewheel_patch_1_13_2()
|
|
12
|
+
del _delvewheel_patch_1_13_2
|
|
13
|
+
# end delvewheel patch
|
|
14
|
+
|
|
15
|
+
import ast
|
|
16
|
+
import importlib
|
|
17
|
+
import importlib.util
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List
|
|
20
|
+
|
|
21
|
+
# Version is managed by setuptools-scm
|
|
22
|
+
try:
|
|
23
|
+
from schubmult._version import version as __version__
|
|
24
|
+
except ImportError:
|
|
25
|
+
__version__ = "5.1.0"
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
Top-level schubmult package initializer.
|
|
29
|
+
|
|
30
|
+
Lazily import and re-export public classes defined in non-experimental submodules.
|
|
31
|
+
Uses deferred AST scanning - only scans files when __dir__() or an unknown
|
|
32
|
+
attribute is accessed for the first time.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Commonly exported helpers - only these are in __all__ initially
|
|
36
|
+
_lazy_exports = {
|
|
37
|
+
# Core Schubert structures
|
|
38
|
+
"Permutation": "schubmult.combinatorics.permutation",
|
|
39
|
+
"uncode": "schubmult.combinatorics.permutation",
|
|
40
|
+
"RCGraph": "schubmult.combinatorics.rc_graph",
|
|
41
|
+
"WCGraph": "schubmult.combinatorics.wc_graph",
|
|
42
|
+
"AntiRCGraph": "schubmult.combinatorics.anti_rc_graph",
|
|
43
|
+
"BoundedRCFactorAlgebra": "schubmult.rings.combinatorial.bounded_rc_factor_algebra",
|
|
44
|
+
"BoundedWCFactorAlgebra": "schubmult.rings.combinatorial.bounded_wc_factor_algebra",
|
|
45
|
+
"ThompsonAlgebra": "schubmult.rings.thompson_algebra",
|
|
46
|
+
"ChuteMoveRing": "schubmult.rings.combinatorial.chute_move_ring",
|
|
47
|
+
"BPD": "schubmult.combinatorics.bpd",
|
|
48
|
+
"ForestRCGraphRing": "schubmult.rings.combinatorial.forest_rc_ring",
|
|
49
|
+
"TileType": "schubmult.combinatorics.bpd",
|
|
50
|
+
# Rings
|
|
51
|
+
"Gx": "schubmult.rings.schubert.grothendieck_ring",
|
|
52
|
+
"DGx": "schubmult.rings.schubert.double_grothendieck_ring",
|
|
53
|
+
"QDGx": "schubmult.rings.schubert.quantum_double_grothendieck_ring",
|
|
54
|
+
"QuantumDoubleGrothendieckRing": "schubmult.rings.schubert.quantum_double_grothendieck_ring",
|
|
55
|
+
"QuantumDoubleGrothendieckElement": "schubmult.rings.schubert.quantum_double_grothendieck_ring",
|
|
56
|
+
"GrothendieckRing": "schubmult.rings.schubert.grothendieck_ring",
|
|
57
|
+
"GrothendieckElement": "schubmult.rings.schubert.grothendieck_ring",
|
|
58
|
+
"Sx": "schubmult.rings.schubert.schubert_ring",
|
|
59
|
+
"DSx": "schubmult.rings.schubert.schubert_ring",
|
|
60
|
+
"ASx": "schubmult.rings.free_algebra",
|
|
61
|
+
"AGx": "schubmult.rings.free_algebra",
|
|
62
|
+
"ADSx": "schubmult.rings.free_algebra",
|
|
63
|
+
"FA": "schubmult.rings.free_algebra",
|
|
64
|
+
"PipeDream": "schubmult.combinatorics.pipe_dream",
|
|
65
|
+
"IncreasingTableau": "schubmult.combinatorics.increasing_tableau",
|
|
66
|
+
"SetValuedTableau": "schubmult.combinatorics.set_valued_tableau",
|
|
67
|
+
"DualRCGraphRing": "schubmult.rings.combinatorial.dual_rc_graph_ring",
|
|
68
|
+
"ForestDual": "schubmult.rings.free_algebra.forest_basis",
|
|
69
|
+
"ForestBasis": "schubmult.rings.free_algebra.forest_basis",
|
|
70
|
+
"DoubleSchubertRing": "schubmult.rings.schubert.schubert_ring",
|
|
71
|
+
"SingleSchubertRing": "schubmult.rings.schubert.schubert_ring",
|
|
72
|
+
"DoubleSchubertElement": "schubmult.rings.schubert.schubert_ring",
|
|
73
|
+
"QSym": "schubmult.rings.quasisymmetric_functions",
|
|
74
|
+
"QSymElement": "schubmult.rings.quasisymmetric_functions",
|
|
75
|
+
"RCGraphRing": "schubmult.rings.combinatorial.rc_graph_ring",
|
|
76
|
+
"GrassRCGraphRing": "schubmult.rings.combinatorial.rc_graph_ring",
|
|
77
|
+
"GrassTensorAlgebra": "schubmult.rings.combinatorial.grass_tensor_algebra",
|
|
78
|
+
"GrassTensorAlgebraElement": "schubmult.rings.combinatorial.grass_tensor_algebra",
|
|
79
|
+
"RCGraphRingElement": "schubmult.rings.combinatorial.rc_graph_ring",
|
|
80
|
+
"WCGraphRing": "schubmult.rings.combinatorial.wc_graph_ring",
|
|
81
|
+
"WCGraphRingElement": "schubmult.rings.combinatorial.wc_graph_ring",
|
|
82
|
+
"EGPlacticRing": "schubmult.rings.combinatorial.eg_plactic_ring",
|
|
83
|
+
"EGPlacticRingElement": "schubmult.rings.combinatorial.eg_plactic_ring",
|
|
84
|
+
"BPDRing": "schubmult.rings.combinatorial.bpd_ring",
|
|
85
|
+
"BPDRingElement": "schubmult.rings.combinatorial.bpd_ring",
|
|
86
|
+
"CrystalGraphTensor": "schubmult.combinatorics.crystal_graph",
|
|
87
|
+
"SchubertMonomialRing": "schubmult.rings.combinatorial.schubert_monomial_ring",
|
|
88
|
+
"SchubertMonomialRingElement": "schubmult.rings.combinatorial.schubert_monomial_ring",
|
|
89
|
+
"PlacticAlgebra": "schubmult.rings.combinatorial.plactic_algebra",
|
|
90
|
+
"PlacticAlgebraElement": "schubmult.rings.combinatorial.plactic_algebra",
|
|
91
|
+
"FreeAlgebra": "schubmult.rings.free_algebra",
|
|
92
|
+
"NSym": "schubmult.rings.nsym",
|
|
93
|
+
"NilHeckeRing": "schubmult.rings.schubert.nil_hecke",
|
|
94
|
+
"PolynomialAlgebra": "schubmult.rings.polynomial_algebra",
|
|
95
|
+
"TensorRing": "schubmult.rings.tensor_ring",
|
|
96
|
+
"DirectProductRing": "schubmult.rings.direct_product_ring",
|
|
97
|
+
"DirectProductRingElement": "schubmult.rings.direct_product_ring",
|
|
98
|
+
"BaseRing": "schubmult.rings.base_ring",
|
|
99
|
+
"BaseRingElement": "schubmult.rings.base_ring",
|
|
100
|
+
# Variables
|
|
101
|
+
"GeneratingSet": "schubmult.symbolic.poly.variables",
|
|
102
|
+
"ChangeOfVariables": "schubmult.symbolic.poly.variables",
|
|
103
|
+
"pull_out_var": "schubmult.utils.schub_lib",
|
|
104
|
+
# Crystal structures
|
|
105
|
+
"Plactic": "schubmult.combinatorics.plactic",
|
|
106
|
+
"NilPlactic": "schubmult.combinatorics.nilplactic",
|
|
107
|
+
"NilPlacticAlgebra": "schubmult.rings.combinatorial.plactic_algebra",
|
|
108
|
+
# Free algebra bases
|
|
109
|
+
"FreeAlgebraBasis": "schubmult.rings.free_algebra",
|
|
110
|
+
"WordBasis": "schubmult.rings.free_algebra",
|
|
111
|
+
"JBasis": "schubmult.rings.free_algebra",
|
|
112
|
+
"JTBasis": "schubmult.rings.free_algebra",
|
|
113
|
+
"SchubertBasis": "schubmult.rings.free_algebra",
|
|
114
|
+
"CompositionSchubertBasis": "schubmult.rings.free_algebra",
|
|
115
|
+
"SchubertSchurBasis": "schubmult.rings.free_algebra",
|
|
116
|
+
"ElementaryBasis": "schubmult.rings.free_algebra",
|
|
117
|
+
"NElementaryBasis": "schubmult.rings.free_algebra",
|
|
118
|
+
"GrothendieckBasis": "schubmult.rings.free_algebra",
|
|
119
|
+
"ZBasis": "schubmult.rings.free_algebra",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
__all__: List[str] = list(_lazy_exports.keys())
|
|
123
|
+
|
|
124
|
+
# Cache for lazy discovery - populated on first access
|
|
125
|
+
_module_map: Dict[str, str] = {}
|
|
126
|
+
_scanned = False
|
|
127
|
+
_package_root = Path(__file__).resolve().parent
|
|
128
|
+
|
|
129
|
+
_package_root = Path(__file__).resolve().parent
|
|
130
|
+
|
|
131
|
+
def _scan_modules():
|
|
132
|
+
"""Deferred AST scan - only called when needed"""
|
|
133
|
+
global _scanned # noqa: PLW0603
|
|
134
|
+
if _scanned:
|
|
135
|
+
return
|
|
136
|
+
_scanned = True
|
|
137
|
+
|
|
138
|
+
_skipped_dirs = {"__pycache__", "tests", "docs", "scripts", "build", "_scripts"}
|
|
139
|
+
experimental_root = _package_root / "combinatorics" / "experimental"
|
|
140
|
+
|
|
141
|
+
for py in _package_root.rglob("*.py"):
|
|
142
|
+
if py.samefile(Path(__file__)):
|
|
143
|
+
continue
|
|
144
|
+
if experimental_root in py.parents:
|
|
145
|
+
continue
|
|
146
|
+
if any(part in _skipped_dirs for part in py.parts):
|
|
147
|
+
continue
|
|
148
|
+
if py.stem.startswith("_") and py.stem != "__init__":
|
|
149
|
+
continue # private modules (e.g. mult._accel) may deliberately reuse public names
|
|
150
|
+
try:
|
|
151
|
+
rel = py.relative_to(_package_root.parent)
|
|
152
|
+
except Exception:
|
|
153
|
+
continue
|
|
154
|
+
modname = ".".join(rel.with_suffix("").parts)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
src = py.read_text(encoding="utf8")
|
|
158
|
+
tree = ast.parse(src)
|
|
159
|
+
except Exception:
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
for node in tree.body:
|
|
163
|
+
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
|
|
164
|
+
name = node.name
|
|
165
|
+
if not name.startswith("_") and name not in _module_map:
|
|
166
|
+
_module_map[name] = modname
|
|
167
|
+
elif isinstance(node, ast.Assign):
|
|
168
|
+
for target in node.targets:
|
|
169
|
+
if isinstance(target, ast.Name):
|
|
170
|
+
name = target.id
|
|
171
|
+
if not name.startswith("_") and name not in _module_map:
|
|
172
|
+
_module_map[name] = modname
|
|
173
|
+
|
|
174
|
+
def __getattr__(name: str):
|
|
175
|
+
"""
|
|
176
|
+
Lazy import exported names. This allows `import schubmult` to succeed
|
|
177
|
+
even if some optional dependencies are missing.
|
|
178
|
+
"""
|
|
179
|
+
if name in globals():
|
|
180
|
+
return globals()[name]
|
|
181
|
+
|
|
182
|
+
# Known lazy exports - try these first without scanning
|
|
183
|
+
if name in _lazy_exports:
|
|
184
|
+
try:
|
|
185
|
+
mod = importlib.import_module(_lazy_exports[name])
|
|
186
|
+
val = getattr(mod, name)
|
|
187
|
+
globals()[name] = val
|
|
188
|
+
return val
|
|
189
|
+
except Exception as e:
|
|
190
|
+
raise AttributeError(f"cannot import {name!r} from {_lazy_exports[name]!r}: {e}") from e
|
|
191
|
+
|
|
192
|
+
# Submodules (e.g. ``from schubmult import schubmult_cpp``) must not trigger the slow scan
|
|
193
|
+
if importlib.util.find_spec(f"{__name__}.{name}") is not None:
|
|
194
|
+
return importlib.import_module(f"{__name__}.{name}")
|
|
195
|
+
|
|
196
|
+
# Not in known exports - scan modules if we haven't yet
|
|
197
|
+
_scan_modules()
|
|
198
|
+
|
|
199
|
+
if name in _module_map:
|
|
200
|
+
modname = _module_map[name]
|
|
201
|
+
try:
|
|
202
|
+
mod = importlib.import_module(modname)
|
|
203
|
+
val = getattr(mod, name)
|
|
204
|
+
globals()[name] = val
|
|
205
|
+
return val
|
|
206
|
+
except Exception as e:
|
|
207
|
+
# If the failing module lives under schubmult._scripts, be lenient:
|
|
208
|
+
# warn the user but return a lightweight proxy that raises a clear
|
|
209
|
+
# error when used. This lets `import schubmult` succeed while still
|
|
210
|
+
# giving a helpful message if the script is actually executed.
|
|
211
|
+
if modname.startswith(__name__ + "._scripts"):
|
|
212
|
+
def _broken_callable(*_, _name=name, _mod=modname, _err=e, **__):
|
|
213
|
+
raise RuntimeError(
|
|
214
|
+
f"attempted to use {_name!r} from {_mod!r} but importing the module failed: {_err!r}",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# attach some attributes to help introspection / debugging
|
|
218
|
+
broken = _broken_callable
|
|
219
|
+
broken.__name__ = f"broken_{name}"
|
|
220
|
+
broken.__doc__ = f"Placeholder for {name} from {modname}; import failed: {e!r}"
|
|
221
|
+
globals()[name] = broken
|
|
222
|
+
return broken
|
|
223
|
+
|
|
224
|
+
# otherwise behave as before
|
|
225
|
+
raise AttributeError(f"cannot import {name!r} from {_module_map[name]!r}: {e}") from e
|
|
226
|
+
|
|
227
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
228
|
+
|
|
229
|
+
def __dir__():
|
|
230
|
+
"""Include lazily-exported names in dir()"""
|
|
231
|
+
_scan_modules() # Ensure we have full module map
|
|
232
|
+
return sorted(set(list(globals().keys()) + list(_lazy_exports.keys()) + list(_module_map.keys())))
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Console and helper scripts shipped with schubmult."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# The CLI never does BLAS work; spinning up numpy's thread pool at import costs ~80 ms wall.
|
|
6
|
+
for _var in ("OPENBLAS_NUM_THREADS", "OMP_NUM_THREADS", "MKL_NUM_THREADS"):
|
|
7
|
+
os.environ.setdefault(_var, "1")
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"""``grothmult_double`` console script: products of double Grothendieck polynomials.
|
|
2
|
+
|
|
3
|
+
Example:
|
|
4
|
+
grothmult_double 3 1 2 - 2 1 3 --display-positive
|
|
5
|
+
grothmult_double --code 2 0 - 1 0 --mixed-var
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from schubmult import Gx, Permutation, uncode
|
|
11
|
+
from schubmult.abc import beta
|
|
12
|
+
from schubmult.mult.groth_double import grothmult_double, mult_poly_groth_double, normalize_coeff
|
|
13
|
+
from schubmult.symbolic import S, sympify, sympify_sympy, expand
|
|
14
|
+
from schubmult.utils.argparse import grothendieck_cli_variables, schub_argparse
|
|
15
|
+
from schubmult.utils.logging import get_logger
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
_posify_cache = {}
|
|
20
|
+
|
|
21
|
+
# graceful failure ceiling: candidate enumeration and the LP grow combinatorially,
|
|
22
|
+
# and unbounded growth has OOM-killed the whole machine before
|
|
23
|
+
_MAX_CANDIDATES = 1_000_000
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _limit_memory():
|
|
27
|
+
"""Cap this process's address space so runaway cases die with MemoryError
|
|
28
|
+
instead of taking down the host (WSL is especially fragile under OOM).
|
|
29
|
+
No-op where ``resource`` is unavailable (Windows)."""
|
|
30
|
+
try:
|
|
31
|
+
import resource
|
|
32
|
+
|
|
33
|
+
import psutil
|
|
34
|
+
|
|
35
|
+
cap = max(int(psutil.virtual_memory().available * 0.7), 1024**3)
|
|
36
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_AS)
|
|
37
|
+
if soft == resource.RLIM_INFINITY or soft > cap:
|
|
38
|
+
resource.setrlimit(resource.RLIMIT_AS, (cap, hard))
|
|
39
|
+
except Exception: # noqa: BLE001
|
|
40
|
+
logger.debug("could not set memory limit", exc_info=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _solver(msg):
|
|
44
|
+
import pulp as pu
|
|
45
|
+
|
|
46
|
+
if pu.HiGHS_CMD().available():
|
|
47
|
+
return pu.HiGHS_CMD(msg=msg)
|
|
48
|
+
from schubmult.mult.positivity import cbc_solver
|
|
49
|
+
|
|
50
|
+
return cbc_solver(msg)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _is_optimal(status):
|
|
54
|
+
"""``LpProblem.solve`` returns an int status in PuLP < 4 and an ``LpSolveStats`` object in PuLP >= 4."""
|
|
55
|
+
if status is None:
|
|
56
|
+
return False
|
|
57
|
+
return int(getattr(status, "status", status)) == 1
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _denominator_budget(den, var2):
|
|
61
|
+
"""Atom multiplicities ``{y_symbol: exp}`` of a denominator ``prod (1 + beta*y_i)**e``.
|
|
62
|
+
|
|
63
|
+
Keys are symengine symbols (consistent with GeneratingSet identity).
|
|
64
|
+
"""
|
|
65
|
+
from sympy import factor_list
|
|
66
|
+
|
|
67
|
+
bs = sympify_sympy(beta)
|
|
68
|
+
budget = {}
|
|
69
|
+
_, factors = factor_list(sympify_sympy(den))
|
|
70
|
+
for fac, mult in factors:
|
|
71
|
+
syms = [s for s in fac.free_symbols if s != bs]
|
|
72
|
+
if len(syms) != 1 or var2.index(syms[0]) == -1:
|
|
73
|
+
raise ValueError(f"unexpected denominator factor {fac}")
|
|
74
|
+
key = sympify(syms[0])
|
|
75
|
+
budget[key] = budget.get(key, 0) + int(mult)
|
|
76
|
+
return budget
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def groth_posify(val, var2, var3, msg):
|
|
80
|
+
"""Positive FGL form of a coefficient, at ``beta = 1``.
|
|
81
|
+
|
|
82
|
+
Basis: all products of ``z_i - y_j``, ``(1 + y_j)^{-1}``, ``(1 + z_i)^{-1}``.
|
|
83
|
+
Substituting ``u_j = 1 + y_j``, ``v_i = 1 + z_i`` (so ``y = u - 1``,
|
|
84
|
+
``z = v - 1``) turns the value into a Laurent polynomial in ``u, v``; clearing
|
|
85
|
+
the monomial denominator makes everything polynomial. Candidates are
|
|
86
|
+
difference products times leftover clearing monomials, expanded once, with
|
|
87
|
+
``as_coefficients_dict`` terms used as opaque basis vectors for an integer
|
|
88
|
+
LP. ``beta`` is restored as ``beta**(#diffs - d)`` with the Laurent atoms
|
|
89
|
+
``(1 + beta*y)`` degree 0 by construction.
|
|
90
|
+
"""
|
|
91
|
+
import itertools
|
|
92
|
+
|
|
93
|
+
import pulp as pu
|
|
94
|
+
from symengine import Symbol as SySymbol
|
|
95
|
+
|
|
96
|
+
from schubmult.symbolic import efficient_subs
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
return int(val)
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
bs = sympify(beta)
|
|
103
|
+
e = sympify(val)
|
|
104
|
+
|
|
105
|
+
# homogeneous degree: yz-degree minus beta-degree is constant across terms
|
|
106
|
+
num0 = expand(sympify(sympify_sympy(e).as_numer_denom()[0]))
|
|
107
|
+
t0 = next(mono for mono, c in num0.as_coefficients_dict().items() if c != 0)
|
|
108
|
+
t0d = t0.as_powers_dict()
|
|
109
|
+
d = sum(int(p) for g, p in t0d.items() if g.is_Symbol and sympify(g) != bs) - int(t0d.get(bs, 0))
|
|
110
|
+
|
|
111
|
+
e1 = expand(efficient_subs(e, {bs: S.One}))
|
|
112
|
+
frees = e1.free_symbols
|
|
113
|
+
ysyms = sorted([s for s in frees if var2.index(s) != -1], key=lambda s: var2.index(s))
|
|
114
|
+
zsyms = sorted([s for s in frees if var3.index(s) != -1], key=lambda s: var3.index(s))
|
|
115
|
+
|
|
116
|
+
uu = {ys: SySymbol(f"u_{var2.index(ys)}") for ys in ysyms}
|
|
117
|
+
vv = {zs: SySymbol(f"v_{var3.index(zs)}") for zs in zsyms}
|
|
118
|
+
back = {u: S.One + bs * sympify(ys) for ys, u in uu.items()}
|
|
119
|
+
back.update({v: S.One + bs * sympify(zs) for zs, v in vv.items()})
|
|
120
|
+
|
|
121
|
+
fwd = {sympify(ys): uu[ys] - S.One for ys in ysyms}
|
|
122
|
+
fwd.update({sympify(zs): vv[zs] - S.One for zs in zsyms})
|
|
123
|
+
laurent = expand(e1.subs(fwd))
|
|
124
|
+
|
|
125
|
+
# clear the (monomial) denominator structurally; everything is a polynomial
|
|
126
|
+
# in u, v from here on and terms are opaque basis vectors
|
|
127
|
+
num_l, den_l = laurent.as_numer_denom()
|
|
128
|
+
num_l = expand(num_l)
|
|
129
|
+
caps = {g: int(p) for g, p in den_l.as_powers_dict().items() if g != S.One and int(p) != 0}
|
|
130
|
+
|
|
131
|
+
# packed-int monomial representation over the u, v generators: PBITS bits
|
|
132
|
+
# of exponent per generator, so monomial products are plain integer sums
|
|
133
|
+
PBITS = 10
|
|
134
|
+
gens = [*[uu[ys] for ys in ysyms], *[vv[zs] for zs in zsyms]]
|
|
135
|
+
gshift = {g: PBITS * i for i, g in enumerate(gens)}
|
|
136
|
+
|
|
137
|
+
def to_vec(expr):
|
|
138
|
+
vec = {}
|
|
139
|
+
degs = {}
|
|
140
|
+
for mono, c in expr.as_coefficients_dict().items():
|
|
141
|
+
if c == 0:
|
|
142
|
+
continue
|
|
143
|
+
key = 0
|
|
144
|
+
deg = 0
|
|
145
|
+
for g, p in mono.as_powers_dict().items():
|
|
146
|
+
if g in gshift:
|
|
147
|
+
key += int(p) << gshift[g]
|
|
148
|
+
deg += int(p)
|
|
149
|
+
elif g != S.One:
|
|
150
|
+
raise ValueError(f"unexpected generator {g}")
|
|
151
|
+
vec[key] = vec.get(key, 0) + int(c)
|
|
152
|
+
degs[key] = deg
|
|
153
|
+
return vec, degs
|
|
154
|
+
|
|
155
|
+
def dict_mul(a, b):
|
|
156
|
+
out = {}
|
|
157
|
+
for m1, c1 in a.items():
|
|
158
|
+
for m2, c2 in b.items():
|
|
159
|
+
k = m1 + m2
|
|
160
|
+
cc = out.get(k, 0) + c1 * c2
|
|
161
|
+
if cc:
|
|
162
|
+
out[k] = cc
|
|
163
|
+
elif k in out:
|
|
164
|
+
del out[k]
|
|
165
|
+
return out
|
|
166
|
+
|
|
167
|
+
vec0, deg0 = to_vec(num_l)
|
|
168
|
+
max_m = max(deg0.values(), default=0)
|
|
169
|
+
# candidates are homogeneous of total degree #pairs + clearing-shift degree,
|
|
170
|
+
# so degrees absent from the target can be pruned losslessly
|
|
171
|
+
target_degs = frozenset(deg0.values())
|
|
172
|
+
|
|
173
|
+
# construction state is shared across coefficients: difference products
|
|
174
|
+
# depend only on the generators, levels also on caps and target degrees;
|
|
175
|
+
# both are built lazily on demand
|
|
176
|
+
gens_key = (tuple(str(s) for s in ysyms), tuple(str(s) for s in zsyms))
|
|
177
|
+
prods_state = _posify_cache.get(gens_key)
|
|
178
|
+
if prods_state is None:
|
|
179
|
+
# difference products built incrementally: each level multiplies by one
|
|
180
|
+
# 2-term pair vector (integer convolution, no symbolic expand)
|
|
181
|
+
pair_vecs = [({1 << gshift[vv[zs]]: 1, 1 << gshift[uu[ys]]: -1}, (ys, zs)) for ys in ysyms for zs in zsyms]
|
|
182
|
+
prods_state = {"pair_vecs": pair_vecs, "prods": [[({0: 1}, (), 0)]]}
|
|
183
|
+
_posify_cache[gens_key] = prods_state
|
|
184
|
+
|
|
185
|
+
cache_key = (gens_key, tuple(sorted((str(g), p) for g, p in caps.items())), target_degs)
|
|
186
|
+
state = _posify_cache.get(cache_key)
|
|
187
|
+
if state is None:
|
|
188
|
+
# clearing monomials as precomputed packed shifts with their degrees
|
|
189
|
+
usage_shifts = []
|
|
190
|
+
for usage in itertools.product(*[range(caps[g] + 1) for g in caps]):
|
|
191
|
+
shift = 0
|
|
192
|
+
sdeg = 0
|
|
193
|
+
for g, a in zip(caps, usage):
|
|
194
|
+
shift += (caps[g] - a) << gshift[g]
|
|
195
|
+
sdeg += caps[g] - a
|
|
196
|
+
usage_shifts.append((shift, dict(zip(caps, usage)), sdeg))
|
|
197
|
+
state = {"usage_shifts": usage_shifts, "levels": {}}
|
|
198
|
+
_posify_cache[cache_key] = state
|
|
199
|
+
|
|
200
|
+
def get_level(m):
|
|
201
|
+
if m in state["levels"]:
|
|
202
|
+
return state["levels"][m]
|
|
203
|
+
prods = prods_state["prods"]
|
|
204
|
+
pair_vecs = prods_state["pair_vecs"]
|
|
205
|
+
shifts = [(shift, usage) for shift, usage, sdeg in state["usage_shifts"] if m + sdeg in target_degs]
|
|
206
|
+
lev = []
|
|
207
|
+
if shifts:
|
|
208
|
+
while len(prods) <= m:
|
|
209
|
+
cur = []
|
|
210
|
+
for vec, combo, start in prods[-1]:
|
|
211
|
+
for idx in range(start, len(pair_vecs)):
|
|
212
|
+
pv, pair = pair_vecs[idx]
|
|
213
|
+
cur.append((dict_mul(vec, pv), (*combo, pair), idx))
|
|
214
|
+
prods.append(cur)
|
|
215
|
+
for vec, combo, _ in prods[m]:
|
|
216
|
+
for shift, usage in shifts:
|
|
217
|
+
lev.append(({k + shift: c for k, c in vec.items()}, combo, usage, m))
|
|
218
|
+
state["levels"][m] = lev
|
|
219
|
+
return lev
|
|
220
|
+
|
|
221
|
+
def solve(candidates):
|
|
222
|
+
lp_prob = pu.LpProblem("Problem", pu.LpMinimize)
|
|
223
|
+
vrs = [lp_prob.add_variable(f"a{i}", lowBound=0, cat="Integer") for i in range(len(candidates))]
|
|
224
|
+
lp_prob += 0
|
|
225
|
+
eqs = {}
|
|
226
|
+
for i, (svec, _, _, _) in enumerate(candidates):
|
|
227
|
+
for k, c in svec.items():
|
|
228
|
+
eqs.setdefault(k, {})[vrs[i]] = c
|
|
229
|
+
for k in set(eqs) | set(vec0):
|
|
230
|
+
# lpSum rather than LpAffineExpression(dict): the latter cannot form constraints in PuLP >= 4
|
|
231
|
+
lp_prob += pu.lpSum(c * v for v, c in eqs.get(k, {}).items()) == vec0.get(k, 0)
|
|
232
|
+
try:
|
|
233
|
+
status = lp_prob.solve(_solver(msg))
|
|
234
|
+
except KeyboardInterrupt:
|
|
235
|
+
import psutil
|
|
236
|
+
|
|
237
|
+
current_process = psutil.Process()
|
|
238
|
+
for child in current_process.children(recursive=True):
|
|
239
|
+
child_process = psutil.Process(child.pid)
|
|
240
|
+
child_process.terminate()
|
|
241
|
+
child_process.kill()
|
|
242
|
+
raise
|
|
243
|
+
# read values here: in PuLP >= 4 variables become unusable once lp_prob is garbage collected
|
|
244
|
+
return status, [v.value() for v in vrs]
|
|
245
|
+
|
|
246
|
+
# escalate the factor-count ceiling: small LPs solve fast and usually suffice
|
|
247
|
+
candidates = []
|
|
248
|
+
status = None
|
|
249
|
+
for m in range(max(d, 0), max_m + 1):
|
|
250
|
+
lev = get_level(m)
|
|
251
|
+
if not lev:
|
|
252
|
+
continue
|
|
253
|
+
candidates = [*candidates, *lev]
|
|
254
|
+
if len(candidates) > _MAX_CANDIDATES:
|
|
255
|
+
raise ValueError(f"candidate set too large ({len(candidates)}) for {val}")
|
|
256
|
+
print(f" solving level m={m}: {len(candidates)} candidates", file=sys.stderr)
|
|
257
|
+
status, values = solve(candidates)
|
|
258
|
+
if _is_optimal(status):
|
|
259
|
+
break
|
|
260
|
+
if not _is_optimal(status):
|
|
261
|
+
raise ValueError(f"no positive representation found for {val}")
|
|
262
|
+
|
|
263
|
+
result = S.Zero
|
|
264
|
+
for i, (_, combo, usage, m) in enumerate(candidates):
|
|
265
|
+
x = values[i]
|
|
266
|
+
# round, don't truncate: solvers return near-integers like 0.9999999999996
|
|
267
|
+
xi = 0 if x is None else round(x)
|
|
268
|
+
if xi != 0:
|
|
269
|
+
term = S.One
|
|
270
|
+
for ys, zs in combo:
|
|
271
|
+
term = term * (sympify(zs) - sympify(ys))
|
|
272
|
+
for g, a in usage.items():
|
|
273
|
+
if a:
|
|
274
|
+
term = term / back[g] ** a
|
|
275
|
+
result += xi * bs ** (m - d) * term
|
|
276
|
+
# exact symbolic verification: structural numerator of the difference
|
|
277
|
+
diff_num, _ = sympify_sympy(result - e).as_numer_denom()
|
|
278
|
+
if expand(sympify(diff_num)) != S.Zero:
|
|
279
|
+
raise ValueError(f"positive representation check failed for {val}: got {result}")
|
|
280
|
+
return result
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def main(argv=None):
|
|
284
|
+
"""Entry point for the ``grothmult_double`` console script.
|
|
285
|
+
|
|
286
|
+
Parses permutations (or, with ``--code``, Lehmer codes) from ``argv``, multiplies
|
|
287
|
+
their double Grothendieck polynomials via `schubmult.mult.groth_double.grothmult_double`,
|
|
288
|
+
and prints the resulting coefficient dictionary ``{Permutation: coefficient}`` in the
|
|
289
|
+
``y``/``z`` coefficient variables and the K-theory parameter ``beta``. With
|
|
290
|
+
``--multiplicative-variables``, use ``beta=-1`` and ``1-1/y_i`` coefficient
|
|
291
|
+
coordinates (also ``1-1/z_i`` for mixed variables). With
|
|
292
|
+
``--display-positive`` (only supported together with ``--mixed-var``), coefficients
|
|
293
|
+
are rewritten in the positive FGL basis of ``z_i - y_j``, ``(1 + y_j)^{-1}``, and
|
|
294
|
+
``(1 + z_i)^{-1}`` via an integer program (see `groth_posify`). Returns the raw
|
|
295
|
+
result dict when the caller passes a ``None`` formatter (e.g. from tests); otherwise
|
|
296
|
+
prints and returns ``None``.
|
|
297
|
+
"""
|
|
298
|
+
if argv is None:
|
|
299
|
+
argv = sys.argv
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
sys.setrecursionlimit(1000000)
|
|
303
|
+
|
|
304
|
+
args, formatter = schub_argparse(
|
|
305
|
+
"grothmult_double",
|
|
306
|
+
"Compute coefficients of products of double Grothendieck polynomials in the same or different sets of coefficient variables",
|
|
307
|
+
argv=argv[1:],
|
|
308
|
+
yz=True,
|
|
309
|
+
coprod=False,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if args.display_positive and args.same:
|
|
313
|
+
print("--display-positive is only supported with --mixed-var for grothmult_double")
|
|
314
|
+
return 1
|
|
315
|
+
|
|
316
|
+
if args.display_positive:
|
|
317
|
+
_limit_memory()
|
|
318
|
+
|
|
319
|
+
mult = args.mult
|
|
320
|
+
mulstring = args.mulstring
|
|
321
|
+
|
|
322
|
+
perms = args.perms
|
|
323
|
+
|
|
324
|
+
ascode = args.ascode
|
|
325
|
+
Permutation.print_as_code = ascode
|
|
326
|
+
pr = args.pr
|
|
327
|
+
beta_value = sympify(args.beta) if args.beta is not None else Gx._beta
|
|
328
|
+
|
|
329
|
+
var2, var3 = grothendieck_cli_variables(args)
|
|
330
|
+
|
|
331
|
+
if ascode:
|
|
332
|
+
perms = [uncode(perm) for perm in perms]
|
|
333
|
+
else:
|
|
334
|
+
for i in range(len(perms)):
|
|
335
|
+
if len(perms[i]) < 2 and (len(perms[i]) == 0 or perms[i][0] == 1):
|
|
336
|
+
perms[i] = Permutation([])
|
|
337
|
+
perms[i] = Permutation(perms[i])
|
|
338
|
+
|
|
339
|
+
coeff_dict = {perms[0]: 1}
|
|
340
|
+
|
|
341
|
+
for perm in perms[1:]:
|
|
342
|
+
coeff_dict = grothmult_double(coeff_dict, perm, var2, var3, beta=beta_value)
|
|
343
|
+
|
|
344
|
+
if mult:
|
|
345
|
+
mul_exp = sympify(mulstring)
|
|
346
|
+
coeff_dict = mult_poly_groth_double(coeff_dict, mul_exp, var2, var3, beta=beta_value)
|
|
347
|
+
|
|
348
|
+
if args.simplify and not args.display_positive:
|
|
349
|
+
if args.multiplicative_variables:
|
|
350
|
+
coeff_dict = {perm: sympify(sympify_sympy(val).cancel()) for perm, val in coeff_dict.items()}
|
|
351
|
+
else:
|
|
352
|
+
coeff_dict = {perm: normalize_coeff(val, var2, beta=beta_value) if beta_value != 0 else expand(val) for perm, val in coeff_dict.items()}
|
|
353
|
+
|
|
354
|
+
positive_coeff_dict = coeff_dict
|
|
355
|
+
if args.display_positive and args.beta is not None:
|
|
356
|
+
# The positivity solver needs the symbolic beta grading before specialization.
|
|
357
|
+
positive_coeff_dict = {perms[0]: 1}
|
|
358
|
+
for perm in perms[1:]:
|
|
359
|
+
positive_coeff_dict = grothmult_double(positive_coeff_dict, perm, var2, var3, beta=Gx._beta)
|
|
360
|
+
if mult:
|
|
361
|
+
positive_coeff_dict = mult_poly_groth_double(positive_coeff_dict, mul_exp, var2, var3, beta=Gx._beta)
|
|
362
|
+
|
|
363
|
+
# sort/filter up front so posified coefficients can stream out as each
|
|
364
|
+
# one finishes (the LPs can take a long time); structural zero test only
|
|
365
|
+
coeff_perms = [perm for perm, val in coeff_dict.items() if val != S.Zero]
|
|
366
|
+
coeff_perms.sort(key=lambda x: (-abs(perms[0].inv + perms[1].inv - x.inv), *x))
|
|
367
|
+
width = max([len(str(perm)) for perm in coeff_perms]) if coeff_perms else 0
|
|
368
|
+
|
|
369
|
+
raw_result_dict = {}
|
|
370
|
+
for i, perm in enumerate(coeff_perms):
|
|
371
|
+
val = coeff_dict[perm]
|
|
372
|
+
if args.display_positive:
|
|
373
|
+
print(f"posify {i + 1}/{len(coeff_perms)}: {str(perm)}", file=sys.stderr)
|
|
374
|
+
try:
|
|
375
|
+
# groth_posify verifies its own output exactly
|
|
376
|
+
val = groth_posify(positive_coeff_dict[perm], var2, var3, args.msg)
|
|
377
|
+
if args.beta is not None:
|
|
378
|
+
val = sympify(val).subs(Gx._beta, beta_value)
|
|
379
|
+
except Exception:
|
|
380
|
+
import traceback
|
|
381
|
+
|
|
382
|
+
traceback.print_exc()
|
|
383
|
+
print(f"error; write to schubmult@gmail.com with the case {perms=} {perm=} {val=}")
|
|
384
|
+
return 1
|
|
385
|
+
raw_result_dict[perm] = val
|
|
386
|
+
if pr and formatter:
|
|
387
|
+
print(f"{str(perm)!s:>{width}} {formatter(val)}", flush=True)
|
|
388
|
+
|
|
389
|
+
if formatter is None:
|
|
390
|
+
return raw_result_dict
|
|
391
|
+
except BrokenPipeError:
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
if __name__ == "__main__":
|
|
396
|
+
sys.exit(main(sys.argv))
|