From 6e94b833158997cbf4503bd2e26a2291cae5fe42 Mon Sep 17 00:00:00 2001 From: dwbmscz <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 19 Feb 2021 14:16:40 -0800 Subject: [PATCH 001/414] Willie's code implementing a cythonized version of the F-matrix code --- src/sage/combinat/root_system/all.py | 1 + src/sage/combinat/root_system/f_matrix.py | 753 ++++++++++++++++++ .../root_system/map_reduce_engine.pxd | 7 + .../root_system/map_reduce_engine.pyx | 118 +++ .../combinat/root_system/poly_tup_engine.pxd | 25 + .../combinat/root_system/poly_tup_engine.pyx | 390 +++++++++ 6 files changed, 1294 insertions(+) create mode 100644 src/sage/combinat/root_system/f_matrix.py create mode 100644 src/sage/combinat/root_system/map_reduce_engine.pxd create mode 100644 src/sage/combinat/root_system/map_reduce_engine.pyx create mode 100644 src/sage/combinat/root_system/poly_tup_engine.pxd create mode 100644 src/sage/combinat/root_system/poly_tup_engine.pyx diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index dfd959e9697..b8e06e4ff3d 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -20,6 +20,7 @@ lazy_import('sage.combinat.root_system.weyl_characters', ['WeylCharacterRing', 'WeightRing']) lazy_import('sage.combinat.root_system.fusion_ring', ['FusionRing']) +lazy_import('sage.combinat.root_system.f_matrix', ['FMatrix']) from .branching_rules import BranchingRule, branching_rule_from_plethysm, branching_rule lazy_import('sage.combinat.root_system.non_symmetric_macdonald_polynomials', 'NonSymmetricMacdonaldPolynomials') diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py new file mode 100644 index 00000000000..50261cd4583 --- /dev/null +++ b/src/sage/combinat/root_system/f_matrix.py @@ -0,0 +1,753 @@ +""" +F-Matrix Factory for FusionRings +""" +# **************************************************************************** +# Copyright (C) 2019 Daniel Bump +# Guillermo Aboumrad +# Travis Scrimshaw +# Galit Anikeeva +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.misc.misc import inject_variable +from sage.matrix.constructor import matrix +from sage.rings.polynomial.all import PolynomialRing +from sage.rings.ideal import Ideal +from sage.combinat.root_system.fusion_ring import FusionRing +import sage.graphs +from sage.graphs.generators.basic import EmptyGraph +from itertools import product +from sage.misc.misc import inject_variable + +#Import pickle for checkpointing and loading certain variables +try: + import cPickle as pickle +except: + import pickle + +from sage.rings.polynomial.polydict import ETuple +# from sage.rings import AlgebraicField as QQbar + +class FMatrix(): + r"""Return an F-Matrix factory for a FusionRing. + + INPUT: + + - ``FR`` -- a FusionRing. + + We only undertake to compute the F-matrix if the + FusionRing is *multiplicity free* meaning that + the Fusion coefficients `N^{ij}_k` are bounded + by 1. For Cartan Types `X_r` and level `k`, + the multiplicity-free cases are given by the + following table. + ++------------------------+----------+ +| Cartan Type | `k` | ++========================+==========+ +| `A_1` | any | ++------------------------+----------+ +| `A_r, r\geq 2` | `\leq 2` | ++------------------------+----------+ +| `B_r, r\geq 2` | `\leq 2` | ++------------------------+----------+ +| `C_2` | `\leq 2` | ++------------------------+----------+ +| `C_r, r\geq 3` | `\leq 1` | ++------------------------+----------+ +| `D_r, r\geq 4` | `\leq 2` | ++------------------------+----------+ +| `G_2,F_4,E_r` | `\leq 2` | ++------------------------+----------+ + + Beyond this limitation, computation of the F-matrix + can involve very large systems of equations. A + rule of thumb is that this code can compute the + F-matrix for systems with `\leq 4` primary fields, + with the exception of `G_2` at level `2`. + + The FusionRing and its methods capture much + of the structure of the underlying tensor category. + But an important aspect that is not encoded in the + fusion ring is the associator, which is a homomorphism + `(A\otimes B)\otimes C\to A\otimes(B\otimes C)` + requires an additional tool, the F-matrix or 6j-symbol. + To specify this, we fix a simple object `D` + and represent the transformation + + .. MATH:: + + \text{Hom}(D,(A\otimes B)\otimes C) \to \text{Hom}(D,A\otimes(B\otimes C)) + + by a matrix `F^{ABC}_D`. This depends on a pair of + additional simple objects `X` and `Y`. Indeed, we can + get a basis for `\text{Hom}(D,(A\otimes B)\otimes C)` + indexed by simple objects `X` in which the corresponding + homomorphism factors through `X\otimes C`, and similarly + `\text{Hom}(D,A\otimes(B\otimes C))` has a basis indexed + by `Y`, in which the basis vector factors through `A\otimes Y`. + + See [TTWL2009]_ for an introduction to this topic, + [EGNO2015]_ Section 4.9 for a precise mathematical + definition and [Bond2007]_ Section 2.5 for a discussion + of how to compute the F-matrix. In addition to + [Bond2007]_ worked out F-matrices may be found in + [RoStWa2009]_ and [CuWa2015]_. + + The F-matrix is only determined up to a gauge. This + is a family of embeddings `C\to A\otimes B` for + simple objects `A,B,C` such that `\text{Hom}(C,A\otimes B)` + is nonzero. Changing the gauge changes the F-matrix though + not in a very essential way. By varying the gauge it is + possible to make the F-matrices unitary, or it is possible + to make them cyclotomic. We choose the latter. + + Due to the large number of equations we may fail to find a + Groebner basis if there are too many variables. + + EXAMPLES:: + + sage: I=FusionRing("E8",2,conjugate=True) + sage: I.fusion_labels(["i0","p","s"],inject_variables=True) + sage: f = FMatrix(I,inject_variables=True); f + creating variables fx1..fx14 + F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients + + We've exported two sets of variables to the global namespace. + We created three variables ``i0, p, s`` to represent the + primary fields (simple elements) of the FusionRing. Creating + the FMatrix factory also created variables ``fx1,fx2, ... , fx14`` + in order to solve the hexagon and pentagon equations describing + the F-matrix. Since we called ``FMatrix`` with the parameter ``inject_variables`` + set true, these have been exported into the global namespace. This + is not necessary for the code to work but if you want to + run the code experimentally you may want access to these + variables. + + EXAMPLES:: + + sage: f.fmatrix(s,s,s,s) + [fx10 fx11] + [fx12 fx13] + + The F-matrix has not been computed at this stage, so + the F-matrix `F^{sss}_s` is filled with variables + ``fx10``, ``fx11``, ``fx12``, ``fx13``. The task is + to solve for these. + + As explained above The F-matrix `(F^{ABC}_D)_{X,Y}` + two other variables `X` and `Y`. We have methods to + tell us (depending on `A,B,C,D`) what the possibilities + for these are. In this example with `A=B=C=D=s` + both `X` and `Y` are allowed to be `i_0` or `s`. + + EXAMPLES:: + + sage: f.f_from(s,s,s,s), f.f_to(s,s,s,s) + ([i0, p], [i0, p]) + + The last two statments show that the possible values of + `X` and `Y` when `A=B=C=D=s` are `i_0` and `p`. + + The F-matrix is computed by solving the so-called + pentagon and hexagon equations. The *pentagon + equations* reflect the Mac Lane pentagon axiom in the + definition of a monoidal category. The hexagon relations + reflect the axioms of a *braided monoidal category*, + which are constraints on both the F-matrix and on + the R-matrix. + + EXAMPLES:: + + sage: f.pentagon()[1:3] + equations: 41 + [-fx0*fx1 + fx1, -fx1*fx2^2 + fx1] + sage: f.hexagon()[1:3] + equations: 14 + [fx1*fx5 + fx2, fx2 + 1] + + You may solve these 41+14=55 equations to compute the F-matrix. + + EXAMPLES:: + + sage: f.get_solution(output=True) + Setting up hexagons and pentagons... + equations: 14 + equations: 37 + Finding a Groebner basis... + Solving... + Fixing the gauge... + adding equation... fx1 - 1 + adding equation... fx11 - 1 + Done! + {(s, s, s, s, i0, i0): (-1/2*zeta128^48 + 1/2*zeta128^16), + (s, s, s, s, i0, p): 1, + (s, s, s, s, p, i0): 1/2, + (s, s, s, s, p, p): (1/2*zeta128^48 - 1/2*zeta128^16), + (s, s, p, i0, p, s): (-1/2*zeta128^48 + 1/2*zeta128^16), + (s, s, p, p, i0, s): (-zeta128^48 + zeta128^16), + (s, p, s, i0, s, s): 1, + (s, p, s, p, s, s): -1, + (s, p, p, s, s, i0): 1, + (p, s, s, i0, s, p): (-zeta128^48 + zeta128^16), + (p, s, s, p, s, i0): (-1/2*zeta128^48 + 1/2*zeta128^16), + (p, s, p, s, s, s): -1, + (p, p, s, s, i0, s): 1, + (p, p, p, p, i0, i0): 1} + + We now have access to the values of the F-mstrix using + the methods :meth:`fmatrix` and :meth:`fmat`. + + EXAMPLES:: + + sage: f.fmatrix(s,s,s,s) + [(-1/2*zeta128^48 + 1/2*zeta128^16) 1] + [ 1/2 (1/2*zeta128^48 - 1/2*zeta128^16)] + sage: f.fmat(s,s,s,s,p,p) + (1/2*zeta128^48 - 1/2*zeta128^16) + + """ + def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): + self.FR = fusion_ring + if self.FR._fusion_labels is None: + self.FR.fusion_labels(fusion_label, inject_variables=True) + #Set up F-symbols entry by entry + n_vars = self.findcases() + self._poly_ring = PolynomialRing(self.FR.field(),n_vars,var_prefix) + if inject_variables: + print ("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) + for i in range(self._poly_ring.ngens()): + inject_variable("%s%s"%(var_prefix,i),self._poly_ring.gens()[i]) + self._var_to_sextuple, self._fvars = self.findcases(output=True) + self._singles = self.singletons() + + #Initialize list of defining equations + self.ideal_basis = list() + + #Initialize empty set of solved F-symbols + self.solved = set() + + #New attributes of the FMatrix class + self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } + self._known_vals = dict() + self.field = self.FR.field() + self.temp_eqns = [] + + ####################### + ### Class utilities ### + ####################### + + def __repr__(self): + """ + EXAMPLES:: + + sage: FMatrix(FusionRing("B2",1)) + F-Matrix factory for The Fusion Ring of Type B2 and level 1 with Integer Ring coefficients + """ + return "F-Matrix factory for %s"%self.FR + + def remaining_vars(self): + """ + Return a list of unknown F-symbols (reflects current stage of computation) + """ + return [var for var in self._poly_ring.gens() if var not in self.solved] + + def clear_equations(self): + """ + Clear the set of equations to be solved. + """ + self.ideal_basis = list() + + def clear_vars(self): + """ + Clear the set of variables. + """ + self._fvars = { self._var_to_sextuple[key] : key for key in self._var_to_sextuple } + self.solved = set() + + def fmat(self, a, b, c, d, x, y, data=True): + """ + Return the F-Matrix coefficient `(F^{a,b,c}_d)_{x,y}` + + EXAMPLES:: + + sage: f=FMatrix(FusionRing("G2",1),fusion_label=["i0","t"]) + sage: [f.fmat(t,t,t,t,x,y) for x in f.FR.basis() for y in f.FR.basis()] + [fx1, fx2, fx3, fx4] + sage: f.get_solution(output=True) + Setting up hexagons and pentagons... + equations: 5 + equations: 10 + Finding a Groebner basis... + Solving... + Fixing the gauge... + adding equation... fx2 - 1 + Done! + {(t, t, t, i0, t, t): 1, + (t, t, t, t, i0, i0): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), + (t, t, t, t, i0, t): 1, + (t, t, t, t, t, i0): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), + (t, t, t, t, t, t): (zeta60^14 - zeta60^6 - zeta60^4 + 1)} + sage: [f.fmat(t,t,t,t,x,y) for x in f.FR.basis() for y in f.FR.basis()] + [(-zeta60^14 + zeta60^6 + zeta60^4 - 1), + 1, + (-zeta60^14 + zeta60^6 + zeta60^4 - 1), + (zeta60^14 - zeta60^6 - zeta60^4 + 1)] + + """ + if self.FR.Nk_ij(a,b,x) == 0 or self.FR.Nk_ij(x,c,d) == 0 or self.FR.Nk_ij(b,c,y) == 0 or self.FR.Nk_ij(a,y,d) == 0: + return 0 + + #Some known zero F-symbols + if a == self.FR.one(): + if x == b and y == d: + return 1 + else: + return 0 + if b == self.FR.one(): + if x == a and y == c: + return 1 + else: + return 0 + if c == self.FR.one(): + if x == d and y == b: + return 1 + else: + return 0 + if data: + #Better to use try/except for speed. Somewhat trivial, but worth + #hours when method is called ~10^11 times + try: + return self._fvars[a,b,c,d,x,y] + except KeyError: + return 0 + else: + return (a,b,c,d,x,y) + + def fmatrix(self,a,b,c,d): + """ + Return the F-Matrix `F^{a,b,c}_d`. + + INPUT: + + - ``a,b,c,d`` -- basis elements of the FusionRing + + EXAMPLES:: + + sage: f=FMatrix(FusionRing("A1",2),fusion_label="c") + sage: f.get_solution(verbose=False); + equations: 14 + equations: 37 + adding equation... fx4 - 1 + adding equation... fx10 - 1 + sage: f.f_from(c1,c1,c1,c1) + [c0, c2] + sage: f.f_to(c1,c1,c1,c1) + [c0, c2] + sage: f.fmatrix(c1,c1,c1,c1) + [ (1/2*zeta32^12 - 1/2*zeta32^4) (-1/2*zeta32^12 + 1/2*zeta32^4)] + [ (1/2*zeta32^12 - 1/2*zeta32^4) (1/2*zeta32^12 - 1/2*zeta32^4)] + """ + + X = self.f_from(a,b,c,d) + Y = self.f_to(a,b,c,d) + return matrix([[self.fmat(a,b,c,d,x,y) for y in Y] for x in X]) + + def findcases(self,output=False): + """ + Return unknown F-matrix entries. If run with output=True, + this returns two dictionaries; otherwise it just returns the + number of unknown values. + + EXAMPLES:: + + sage: f=FMatrix(FusionRing("G2",1),fusion_label=["i0","t"]) + sage: f.findcases() + 5 + sage: f.findcases(output=True) + ({fx4: (t, t, t, t, t, t), + fx3: (t, t, t, t, t, i0), + fx2: (t, t, t, t, i0, t), + fx1: (t, t, t, t, i0, i0), + fx0: (t, t, t, i0, t, t)}, + {(t, t, t, i0, t, t): fx0, + (t, t, t, t, i0, i0): fx1, + (t, t, t, t, i0, t): fx2, + (t, t, t, t, t, i0): fx3, + (t, t, t, t, t, t): fx4}) + + """ + i = 0 + if output: + idx_map = dict() + ret = dict() + for (a,b,c,d) in list(product(self.FR.basis(), repeat=4)): + for x in self.f_from(a, b, c, d): + for y in self.f_to(a, b, c, d): + fm = self.fmat(a, b, c, d, x, y, data=False) + if fm is not None and fm not in [0,1]: + if output: + v = self._poly_ring.gens()[i] + ret[(a,b,c,d,x,y)] = v + idx_map[v] = (a, b, c, d, x, y) + i += 1 + if output: + return idx_map, ret + else: + return i + + def singletons(self): + """ + Find x_i that are automatically nonzero, because their F-matrix is 1x1 + """ + ret = [] + for (a, b, c, d) in list(product(self.FR.basis(), repeat=4)): + (ff,ft) = (self.f_from(a,b,c,d),self.f_to(a,b,c,d)) + if len(ff) == 1 and len(ft) == 1: + v = self._fvars.get((a,b,c,d,ff[0],ft[0]), None) + if v in self._poly_ring.gens(): + ret.append(v) + return ret + + def f_from(self,a,b,c,d): + r""" + Return the possible `x` such that there are morphisms + `d\to x\otimes c\to (a\otimes b)\otimes c`. + + INPUT: + + - ``a,b,c,d`` -- basis elements of the FusionRing. + + EXAMPLES:: + + sage: f=FMatrix(FusionRing("A1",3),fusion_label="a") + sage: f.fmatrix(a1,a1,a2,a2) + [fx6 fx7] + [fx8 fx9] + sage: f.f_from(a1,a1,a2,a2) + [a0, a2] + sage: f.f_to(a1,a1,a2,a2) + [a1, a3] + """ + + return [x for x in self.FR.basis() if self.FR.Nk_ij(a,b,x) != 0 and self.FR.Nk_ij(x,c,d) != 0] + + def f_to(self,a,b,c,d): + r""" + Return the possible `y` such that there are morphisms + `d\to a\otimes y\to a\otimes(b\otimes c)`. + + INPUT: + + - ``a,b,c,d`` -- basis elements of the FusionRing. + + EXAMPLES:: + + sage: B=FMatrix(FusionRing("B2",2),fusion_label="b") + sage: B.fmatrix(b2,b4,b4,b2) + [fx278 fx279 fx280] + [fx281 fx282 fx283] + [fx284 fx285 fx286] + sage: B.f_from(b2,b4,b4,b2) + [b1, b3, b5] + sage: B.f_to(b2,b4,b4,b2) + [b0, b1, b5] + + """ + + return [y for y in self.FR.basis() if self.FR.Nk_ij(b,c,y) != 0 and self.FR.Nk_ij(a,y,d) != 0] + + #################### + ### Data getters ### + #################### + + def get_fmats_in_alg_field(self): + return { sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items() } + + #Return radical expression for fmats for easy visualization + def get_radical_expression(self): + return { sextuple : val.radical_expression() for sextuple, val in get_fmats_in_alg_field().items() } + + #Construct a dictionary of idx, known_val pairs for equation substitution + def get_known_vals(self): + return { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in self.solved } + + #Construct an ETuple indicating positions of known nonzero variables. + #MUST be called after fmats._ks = get_known_sq() + def get_known_nonz(self): + nonz = { self._var_to_idx[var] : 100 for var in self._singles } + for idx in self._ks: + nonz[idx] = 100 + return ETuple(nonz, fmats._poly_ring.ngens()) + + ############################## + ### Variables partitioning ### + ############################## + + #Get the size of the largest F-matrix F^{abc}_d + def largest_fmat_size(self): + return max(self.fmatrix(*tup).nrows() for tup in product(self.FR.basis(),repeat=4)) + + #Partition the F-symbols according to the size of the F-matrix F^{abc}_d + #they belong to + def get_fmats_by_size(self,n): + fvars_copy = deepcopy(self._fvars) + self.clear_vars() + var_set = set() + for quadruple in product(self.FR.basis(),repeat=4): + F = self.fmatrix(*quadruple) + #Discard trivial 1x1 F-matrix, if applicable + if F.nrows() == n and F.coefficients() != [1]: + var_set.update(self._var_to_idx[fx] for fx in F.coefficients()) + self._fvars = fvars_copy + return var_set + + ############################ + ### Checkpoint utilities ### + ############################ + + #Auto-generate filename string + def get_fr_str(self): + return self.FR.cartan_type()[0] + str(self.FR.cartan_type()[1]) + str(self.FR.fusion_level()) + + def save_fvars(self,filename): + with open(filename, 'wb') as f: + pickle.dump(self._fvars, f) + # pickle.dump([fmats._fvars, fmats.solved], f) + + #If provided, optional param save_dir should have a trailing forward slash + def load_fvars(self,save_dir=""): + with open(save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle", 'rb') as f: + self._fvars = pickle.load(f) + # fmats._fvars, fmats.solved = pickle.load(f) + + ######################## + ### Equations set up ### + ######################## + + def poly_to_tup(self,poly): + return tuple(poly.dict().items()) + + def tup_to_poly(self,eq_tup,parent=None): + if parent is None: + parent = self._poly_ring + return parent({ exp : coeff for exp, coeff in eq_tup }) + + def get_orthogonality_constraints(self,output=True): + eqns = list() + for tup in product(self.FR.basis(), repeat=4): + mat = self.fmatrix(*tup) + eqns.extend((mat.T * mat - matrix.identity(mat.nrows())).coefficients()) + if output: + return eqns + self.ideal_basis.extend([self.poly_to_tup(eq) for eq in eqns]) + + def feq(self, a, b, c, d, e, f, g, k, l, prune=False): + """ + Return True if the Pentagon axiom ([Bond2007]_ (2.77)) is satisfied. + """ + lhs = self.fmat(f,c,d,e,g,l)*self.fmat(a,b,l,e,f,k) + rhs = sum(self.fmat(a,b,c,g,f,h)*self.fmat(a,h,d,e,g,k)*self.fmat(b,c,d,k,h,l) for h in self.FR.basis()) + if lhs != 0 or not prune: # it is believed that if lhs=0, the equation carries no new information + return lhs - rhs + else: + return 0 + + def req(self, a, b, c, d, e, g, side="left"): + """ + Return A hexagon equation (Bond[2007]_ (2.78) or (2.79)). + + INPUT: + + - ``a,b,c,d,e,f`` -- basis elements of the FusionRing + - ``side`` -- (default left) which hexagon axiom to use + + """ + if side == "left": + lhs = self.FR.r_matrix(a,c,e)*self.fmat(a,c,b,d,e,g)*self.FR.r_matrix(b,c,g) + rhs = sum(self.fmat(c,a,b,d,e,f)*self.FR.r_matrix(f,c,d)*self.fmat(a,b,c,d,f,g) for f in self.FR.basis()) + elif side == "right": + # r(a,b,x) is a root of unity, so its inverse is its complex conjugate + lhs = self.FR.r_matrix(c,a,e).conjugate()*self.fmat(a,c,b,d,e,g)*self.FR.r_matrix(c,b,g).conjugate() + rhs = sum(self.fmat(c,a,b,d,e,f)*self.FR.r_matrix(c,f,d).conjugate()*self.fmat(a,b,c,d,f,g) for f in self.FR.basis()) + return lhs-rhs + + def pentagon(self, verbose=False, output=True, factor=False, prune=False): + """ + Return generators of the ideal of Pentagon equations. + + INPUT: + + - ``verbose`` -- (optional) set True for verbose. Default False + - ``output`` -- (optional) set True to output a set of equations. Default True + - ``factor`` -- (optional) set True for sreduce simplified equations. + + In contrast with the hexagon equations, where setting ``factor`` True + is a big improvement, for the pentagon equations this option produces + little or no simplification. So the default is False. + + EXAMPLES:: + + sage: p = FMatrix(FusionRing("A2",1),fusion_label="c") + sage: p.pentagon()[-3:] + equations: 16 + [-fx5*fx6 + fx1, -fx4*fx6*fx7 + fx2, -fx5*fx7^2 + fx3*fx6] + sage: p.pentagon(factor=True)[-3:] + equations: 16 + [fx5*fx6 - fx1, fx4*fx6*fx7 - fx2, fx5*fx7^2 - fx3*fx6] + + """ + if output: + ret = [] + for (a,b,c,d,e,f,g,k,l) in list(product(self.FR.basis(), repeat=9)): + pd = self.feq(a,b,c,d,e,f,g,k,l,prune=prune) + if pd != 0: + if factor: + pd = self.sreduce(pd) + if output: + ret.append(pd) + if verbose: + print ("%s,%s,%s,%s,%s,%s,%s,%s,%s : %s"%(a,b,c,d,e,f,g,k,l,pd)) + print ("equations: %s"%len(ret)) + if output: + return ret + + ##################### + ### Graph methods ### + ##################### + + def equations_graph(self,eqns=None): + """ + Return a graph whose vertices are variables + and whose edges correspond to equations + relating two variables. + + This graph may contain isolated vertices... + + INPUT: + + - ``equations`` -- a list of equations + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: G = f.equation_graph(f.hexagon(factor=True)) + equations: 71 + sage: G.connected_components_number() + 14 + sage: G1=G.connected_components_subgraphs()[0] + sage: G1.size() + 60 + sage: G1.is_regular() + True + + """ + if eqns is None: + eqns = self.ideal_basis + + G = sage.graphs.generators.basic.EmptyGraph() + G.add_vertices([x for eq_tup in eqns for x in variables(eq_tup)]) + for eq_tup in eqns: + s = [v for v in variables(eq_tup)] + for x in s: + for y in s: + if y!=x: + G.add_edge(x,y) + return G + + ####################### + ### Solution method ### + ####################### + + def get_solution(self, equations=None, factor=True, verbose=True, prune=True, algorithm='', output=False): + """ + Solve the the hexagon and pentagon relations to evaluate the F-matrix. + + INPUT: + + - ``equations`` -- (optional) a set of equations to be + solved. Defaults to the hexagon and pentagon equations. + - ``factor`` -- (default: ``True``). Set true to use + the sreduce method to simplify the hexagon and pentagon + equations before solving them. + - ``algorithm`` -- (optional). Algorithm to compute Groebner Basis. + - ``output`` -- (optional, default False). Output a dictionary of + F-matrix values. This may be useful to see but may be omitted + since this information will be available afterwards via the + :meth:`fmatrix` and :meth:`fmat` methods. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",1),fusion_label="a") + sage: f.get_solution(verbose=False,output=True) + equations: 8 + equations: 16 + adding equation... fx4 - 1 + {(a2, a2, a2, a0, a1, a1): 1, + (a2, a2, a1, a2, a1, a0): 1, + (a2, a1, a2, a2, a0, a0): 1, + (a2, a1, a1, a1, a0, a2): 1, + (a1, a2, a2, a2, a0, a1): 1, + (a1, a2, a1, a1, a0, a0): 1, + (a1, a1, a2, a1, a2, a0): 1, + (a1, a1, a1, a0, a2, a2): 1} + + After you successfully run ``get_solution`` you may check + the correctness of the F-matrix by running :meth:`hexagon` + and :meth:`pentagon`. These should return empty lists + of equations. In this example, we turn off the factor + and prune optimizations to test all instances. + + EXAMPLES:: + + sage: f.hexagon(factor=False) + equations: 0 + [] + sage: f.hexagon(factor=False,side="right") + equations: 0 + [] + sage: f.pentagon(factor=False,prune=False) + equations: 0 + [] + + """ + if equations is None: + if verbose: + print("Setting up hexagons and pentagons...") + equations = self.hexagon(verbose=False, factor=factor)+self.pentagon(verbose=False, factor=factor, prune=prune) + if verbose: + print("Finding a Groebner basis...") + self.ideal_basis = set(Ideal(equations).groebner_basis(algorithm=algorithm)) + if verbose: + print("Solving...") + self.substitute_degree_one() + if verbose: + print("Fixing the gauge...") + self.fix_gauge(algorithm=algorithm) + if verbose: + print("Done!") + if output: + return self._fvars + + ##################### + ### Verifications ### + ##################### + + #Ensure the hexagon equations are satisfied + def verify_hexagons(self): + hex = [] + for a,b,c,d,e,g in product(self.FR.basis(),repeat=6): + lhs = self.field(self.FR.r_matrix(a,c,e))*self.fmat(a,c,b,d,e,g)*self.field(self.FR.r_matrix(b,c,g)) + rhs = sum(self.fmat(c,a,b,d,e,f)*self.field(self.FR.r_matrix(f,c,d))*self.fmat(a,b,c,d,f,g) for f in self.FR.basis()) + hex.append(lhs-rhs) + return list(set(hex)) + + #Verify that all F-matrices are real and unitary (orthogonal) + def fmats_are_orthogonal(self): + is_orthog = [] + for a,b,c,d in product(self.FR.basis(),repeat=4): + mat = self.fmatrix(a,b,c,d) + is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) + return all(is_orthog) diff --git a/src/sage/combinat/root_system/map_reduce_engine.pxd b/src/sage/combinat/root_system/map_reduce_engine.pxd new file mode 100644 index 00000000000..15b5aeb0607 --- /dev/null +++ b/src/sage/combinat/root_system/map_reduce_engine.pxd @@ -0,0 +1,7 @@ +cdef class MapReduceEngine(): + cdef public list worker_results + cdef public object input_iter + cdef public int mp_thresh + + cpdef map_caller(self,mp_params,mapper,extra_args=*) + diff --git a/src/sage/combinat/root_system/map_reduce_engine.pyx b/src/sage/combinat/root_system/map_reduce_engine.pyx new file mode 100644 index 00000000000..3bd01615aac --- /dev/null +++ b/src/sage/combinat/root_system/map_reduce_engine.pyx @@ -0,0 +1,118 @@ +############################ +### Parlallel processing ### +############################ +from functools import partial +cimport cython + +#Rudimentary framework for performing MapReduce style computations +#DISCLAIMER: does not +cdef class MapReduceEngine(): + def __init__(self, mp_thresh=2500): + self.worker_results = list() + self.input_iter = None + #Multiprocess size parameter + self.mp_thresh = mp_thresh + + ################ + ### Reducers ### + ################ + + #Helper function for returning processed results back to parent process + #Trivial reducer: simply collects objects with the same key in the worker + def reduce_single_proc(self,proc): + #Discard the zero polynomial + reduced = set(self.worker_results)-set([tuple()]) + self.worker_results = list() + return list(reduced) + + #All-to-one communication step + def reduce_multi_process(self,reducer,worker_pool): + collected_eqns = set() + for child_eqns in worker_pool.imap_unordered(reducer,range(worker_pool._processes)): + collected_eqns.update(child_eqns) + return list(collected_eqns) + + ################# + ### MapReduce ### + ################# + + #Map fn across input_iter and reduce output to polynomial + #If worker_pool is not provided, function maps and reduces on a single process. + #If worker_pool is provided, the function attempts to determine whether it should + #use multiprocessing based on the length of the input iterable. If it can't determine + #the length of the input iterable then it uses multiprocessing with the default chunksize of 1 + #if chunksize is not explicitly provided. + def map_triv_reduce(self,mapper,input_iter,reducer=None,worker_pool=None,chunksize=None,mp_thresh=None): + #Compute multiprocessing parameters + if worker_pool is not None: + try: + n = len(input_iter) + except: + n = mp_thresh + 1 + if chunksize is None: + chunksize = n // (worker_pool._processes**2) + 1 + if mp_thresh is None: + mp_thresh = self.mp_thresh + no_mp = worker_pool is None or n < mp_thresh + #Map phase. Casting Async Object blocks execution... Each process holds results + #in its copy of fmats.temp_eqns + if no_mp: + list(map(mapper,input_iter)) + else: + list(worker_pool.imap_unordered(mapper,input_iter,chunksize=chunksize)) + + #Early termination in case no reducer is provided + if reducer is None: + return + + #Reduce phase + if no_mp: + results = reducer(0) + else: + results = self.reduce_multi_process(reducer,worker_pool) + return results + + #Map the given mapper across the input iterable creted using + #input_iter_setter, and then reduce using the provided reducer. + #The input iterable is created in each worker process with minimal communication: + #the parent only tells woker how to create iterable, but does NOT send an + #iterator object or its contents + + ###INPUT: + #input_iter_setter is a function that takes as a single argument an integer + #0 <= proc <= worker_pool._processes. If worker_pool is None, then + #input_iter_setter(0) is called + #The function input_iter_setter should set mr_eng.input_iter to a desired value + #mapper is a function that calls mr_eng.map_caller with appropriate arguments, + #that are bound to the function inside each worker process + #mapper does NOT return; it appends results to mr_eng.worker_results + #reducer is a function that instructs the engine how to collect results + #corresponding to the same key in each worker process + #input_iter_setter, mapper, AND reducer are instructions that will be + #processed inside each worker in the provided pool + + ###NOTES: set_input_iter MUST be called BEFORE + def emap_no_comm(self,input_iter_setter,mapper,reducer,worker_pool=None): + n_proc = worker_pool._processes if worker_pool is not None else 1 + params = [(child_id, n_proc) for child_id in range(n_proc)] + self.map_triv_reduce(input_iter_setter,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + return self.map_triv_reduce(mapper,params,reducer,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + + @cython.wraparound(False) + @cython.nonecheck(False) + @cython.cdivision(True) + cpdef map_caller(self,mp_params,mapper,extra_args=None): + cdef int child_id, i, n_proc + child_id, n_proc = mp_params + #Plug in additional arguments, if any + if extra_args is not None: + mapper = partial(mapper,*extra_args) + if self.input_iter is None: + raise ValueError("set_input_iter must be used to create input iterable in each worker process") + for i, input in enumerate(self.input_iter): + if i % n_proc == child_id: + res = mapper(input) + if res: + self.worker_results.append(res) + #Re-set iterator + self.input_iter = None diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd new file mode 100644 index 00000000000..28f5d9138bc --- /dev/null +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -0,0 +1,25 @@ +from sage.rings.polynomial.polydict cimport ETuple + +cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) +cdef ETuple degrees(tuple poly_tup) +cpdef ETuple get_variables_degrees(list eqns) +cpdef list variables(tuple eq_tup) +cpdef constant_coeff(tuple eq_tup) +cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) +cpdef bint tup_fixes_sq(tuple eq_tup) +cpdef dict subs_squares(dict eq_dict, dict known_sq) +cdef dict remove_gcf(dict eq_dict, ETuple nonz) +cdef tuple to_monic(dict eq_dict) +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq) +cpdef dict compute_known_powers(ETuple max_deg, dict val_dict) +cpdef dict subs(tuple poly_tup, dict known_powers) +cdef tuple tup_mul(tuple p1, tuple p2) +cpdef update_reduce(tuple eq_tup, factory, mr_eng) +cpdef int poly_tup_cmp(tuple tleft, tuple tright) +cpdef tuple req_cy(factory, tuple sextuple, side=*) +cpdef tuple feq_cy(factory, tuple nonuple, bint prune=*) +cpdef feq_verif(factory, tuple nonuple, float tol=*) +cpdef pent_verify(tuple mp_params, factory) +cpdef tuple rationalify(factory, tuple eq_tup) +cpdef tuple cyclotomify(factory, tuple rat_tup) + diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx new file mode 100644 index 00000000000..02c7a6f7c2e --- /dev/null +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -0,0 +1,390 @@ +from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular +from sage.rings.polynomial.polydict cimport ETuple, PolyDict +from sage.rings.polynomial.term_order import TermOrder +from sage.rings.rational_field import RationalField as QQ + +###NOTE: issues with importing NumberFieldElement_absolute and FMatrix +#from sage.rings.number_field.number_field_element cimport NumberFieldElement +#from sage.combinat.root_system.f_matrix import FMatrix + +################################################### +### Arithmetic Engine for polynomials as tuples ### +################################################### + +########### +### API ### +########### + +#Convert a polynomial object into the internal representation as tuple of +#(ETuple exp, NumberFieldElement coeff) pairs +cpdef tuple poly_to_tup(MPolynomial_libsingular poly): + return tuple(poly.dict().items()) + +#Return a polynomial object from its tuple representation. Inverse of poly_to_tup. +#poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup && tup_to_poly(poly_to_tup(eq), eq.parent()) == eq +#Assumes parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup +cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent): + # Maybe the following is faster but we need to ensure all coefficients are + # already in fmats._poly_ring.base_ring() so that implicit casting is avoided + # (this is pretty slow) + # return parent._element_constructor_({ exp : c for exp, c in tup_of_pairs }, check=False) + return parent(dict(eq_tup)) + +###################### +### "Change rings" ### +###################### + +#Given a tuple of pairs representing a univariate polynomial and a univariate +#polynomial ring generator, return a univariate polynomial object +def tup_to_univ_poly(eq_tup, gen): + univ_tup = tuple((exp.nonzero_values()[0] if exp.nonzero_values() else 0, c) for exp, c in eq_tup) + return sum(c * gen ** p for p, c in univ_tup) + +#Return a tuple representing a polynomial in a ring with len(sorted_vars) generators +#This method is used for creating polynomial objects with the "right number" of +#variables for computing Groebner bases of the partitioned equations graph +#and for adding constraints ensuring certain F-symbols are nonzero +cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars): + cdef ETuple new_e + cdef list resized = list() + for exp, c in eq_tup: + new_e = ETuple({ idx_map[pos] : d for pos, d in exp.sparse_iter() }, nvars) + resized.append((new_e,c)) + return tuple(resized) + +########################### +### Convenience methods ### +########################### + +#Return the maximal degree of each variable in the polynomial +cdef ETuple degrees(tuple poly_tup): + #Deal with the empty tuple, representing the zero polynomial + if not poly_tup: return ETuple() + cdef ETuple max_degs, exp + max_degs = poly_tup[0][0] + for exp, c in poly_tup[1:]: + max_degs = max_degs.emax(exp) + return max_degs + +#Find maximum degrees for each variable in equations +cpdef ETuple get_variables_degrees(list eqns): + cdef tuple eq_tup + cdef ETuple max_deg + max_deg = degrees(eqns[0]) + for eq_tup in eqns[1:]: + max_deg = max_deg.emax(degrees(eq_tup)) + return max_deg + +#Return indices of all variables appearing in eq_tup +cpdef list variables(tuple eq_tup): + return degrees(eq_tup).nonzero_positions() + +#Return the constant coefficient of the polynomial represented by given tuple +cpdef constant_coeff(tuple eq_tup): + for exp, coeff in eq_tup: + if exp.is_constant(): + return coeff + return 0 + +cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): + cdef list new_tup = list() + for exp, coeff in eq_tup: + new_tup.append((exp, coeff_map(coeff))) + return tuple(new_tup) + +#Determine if given equation fixes the square of a variable. An equation +#fixes the sq of a variable if it is of the form a*x^2 + c for NONZERO constants a, c +cpdef bint tup_fixes_sq(tuple eq_tup): + return len(eq_tup) == 2 and len(variables(eq_tup)) == 1 and eq_tup[0][0].nonzero_values() == [2] + +###################### +### Simplification ### +###################### + +#Substitutes for known squares in a given polynomial. +#The parameter known_sq is a dictionary of (int i, NumberFieldElement a) pairs such that x_i^2 - a == 0 +#Returns a dictionary of (ETuple, coeff) pairs representing polynomial +cpdef dict subs_squares(dict eq_dict, dict known_sq): + cdef dict subbed, new_e + cdef ETuple exp, lm + cdef int idx, power + subbed = dict() + for exp, coeff in eq_dict.items(): + new_e = dict() + for idx, power in exp.sparse_iter(): + if idx in known_sq: + coeff *= known_sq[idx] ** (power // 2) + #New power is 1 if power is odd + if power & True: + new_e[idx] = 1 + else: + new_e[idx] = power + exp = ETuple(new_e, len(exp)) + #If exponent tuple is already present in dictionary, coefficients are added + if exp in subbed: + subbed[exp] += coeff + else: + subbed[exp] = coeff + return { exp : a for exp, a in subbed.items() } + +#Returns a dictionary of (ETuple, coeff) pairs describing the polynomial eq / GCF(eq) +#The input nonz is an ETuple indicating the positions of variables known to be nonzero. +#The entries of nonz are assumed to be some relatively large number, like 100 +cdef dict remove_gcf(dict eq_dict, ETuple nonz): + #Find common variables, filtered according to known nonzeros + cdef ETuple common_powers, exp + common_powers = nonz + for exp, c in eq_dict.items(): + common_powers = common_powers.emin(exp) + return { exp.esub(common_powers) : c for exp, c in eq_dict.items() } + +#Return tuple of pairs (ETuple, coeff) describing the monic polynomial associated to eq_dict +#Here, the leading coefficient is chosen according to the degree reverse lexicographic ordering +#(default for multivariate polynomial rings) +cdef tuple to_monic(dict eq_dict): + if not eq_dict: return tuple() + it = reversed(sorted(eq_dict, key=TermOrder().sortkey_degrevlex)) + lm = next(it) + lc = eq_dict[lm] + if not lc: return tuple() + cdef ETuple exp + cdef list ret = [(lm, QQ().one())] + inv_lc = lc.inverse_of_unit() + for exp in it: + ret.append((exp, inv_lc * eq_dict[exp])) + return tuple(ret) + +# Return a dictionary describing a monic polynomial with no known nonzero gcd and +# no known squares +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): + if not eq_dict: return tuple() + return to_monic(remove_gcf(subs_squares(eq_dict, known_sq), nonz)) + +#################### +### Substitution ### +#################### + +#Pre-compute powers of known values for efficiency +cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): + assert max(max_deg.nonzero_values(sort=False)) <= 100, "NotImplementedError: Cannot substitute for degree larger than 100" + max_deg = max_deg.emin(ETuple({ idx : 100 for idx in val_dict }, len(max_deg))) + cdef dict known_powers + #Get polynomial unit as tuple to initialize list elements + cdef tuple one = tuple(((ETuple({},len(max_deg)),QQ().one()),)) + cdef int d, var_idx + known_powers = { var_idx : [one]*(d+1) for var_idx, d in max_deg.sparse_iter() } + for var_idx, d in max_deg.sparse_iter(): + for power in range(d): + known_powers[var_idx][power+1] = tup_mul(known_powers[var_idx][power],val_dict[var_idx]) + return known_powers + +#Substitute given variables into a polynomial tuple +cpdef dict subs(tuple poly_tup, dict known_powers): + cdef dict subbed = {} + cdef ETuple exp, m + cdef int var_idx, power + cdef tuple temp + for exp, coeff in poly_tup: + #Get polynomial unit as tuple + temp = tuple(((ETuple({},len(exp)),QQ().one()),)) + for var_idx, power in exp.sparse_iter(): + if var_idx in known_powers: + exp = exp.eadd_p(-power,var_idx) + temp = tup_mul(temp,known_powers[var_idx][power]) + for m, c in temp: + if exp.eadd(m) in subbed: + subbed[exp.eadd(m)] += coeff*c + else: + subbed[exp.eadd(m)] = coeff*c + return subbed + +#Multiplication of two tuples... may have to make this faster +cdef tuple tup_mul(tuple p1, tuple p2): + cdef dict prod = dict() + cdef ETuple xi, yj + for xi, ai in p1: + for yj, bj in p2: + if xi.eadd(yj) in prod: + prod[xi.eadd(yj)] += ai*bj + else: + prod[xi.eadd(yj)] = ai*bj + return tuple(prod.items()) + +#Substitute known values, known squares, and reduce! +cpdef update_reduce(tuple eq_tup, factory, mr_eng): + cdef dict eq_dict = subs(eq_tup,factory._kp) + cdef reduced + if tup_fixes_sq(tuple(eq_dict.items())): + reduced = to_monic(eq_dict) + else: + reduced = reduce_poly_dict(eq_dict,factory._nnz,factory._ks) + mr_eng.worker_results.append(reduced) + +############### +### Sorting ### +############### + +#Determine which polynomial is larger with respect to the degrevlex ordering +cpdef int poly_tup_cmp(tuple tleft, tuple tright): + cdef int i, ret, sf, sg, val + cdef ETuple f, g + ret = 0 + for i in range(min(len(tleft),len(tright))): + f, g = tleft[i][0], tright[i][0] + if f == g: + if tleft[i][1] != tright[i][1]: + ret = -1 + 2*(tleft[i][1] > tright[i][1]) + else: + sf, sg = 0, 0 + for val in f.nonzero_values(sort=False): + sf += val + for val in g.nonzero_values(sort=False): + sg += val + ret = -1 + 2*(sf > sg or ( sf == sg and f.reversed() < g.reversed() )) + if ret != 0: + return ret + return len(tleft) - len(tright) + +############################ +### Fast FMatrix methods ### +############################ +from itertools import product + +#Given an FMatrix factory and a sextuple, return a pentagon equation as a polynomial object +cpdef tuple req_cy(factory, tuple sextuple, side="left"): + a, b, c, d, e, g = sextuple + #To add typing we need to ensure all fmats.fmat are of the same type? + #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? + lhs = factory.FR.r_matrix(a,c,e)*factory.fmat(a,c,b,d,e,g)*factory.FR.r_matrix(b,c,g) + rhs = 0 + for f in factory.FR.basis(): + rhs += factory.fmat(c,a,b,d,e,f)*factory.FR.r_matrix(f,c,d)*factory.fmat(a,b,c,d,f,g) + he = lhs - rhs + if he: + return reduce_poly_dict(he.dict(),factory._nnz,factory._ks) + else: + return tuple() + +#Set up and reduce the hexagon equations corresponding to this worker +# cpdef get_reduced_hexagons(factory, int child_id, int n_proc): +# cdef int i +# cdef tuple sextuple +# for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): +# if i % n_proc == child_id: +# he = req_cy(factory,sextuple) +# if he: +# factory.temp_eqns.append(reduce_poly_dict(he.dict(),factory._nnz,factory._ks)) + +#Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object + +cpdef tuple feq_cy(factory, tuple nonuple, bint prune=False): + a, b, c, d, e, f, g, k, l = nonuple + cdef lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) + if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information + return factory._poly_ring.zero() + cdef MPolynomial_libsingular rhs = factory._poly_ring.zero() + for h in factory.FR.basis(): + rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) + return reduce_poly_dict((lhs - rhs).dict(),factory._nnz,factory._ks) + +#Set up and reduce the pentagon equations corresponding to this worker +# cpdef get_reduced_pentagons(factory, int child_id, int n_proc, bint prune=True): +# cdef int i +# cdef tuple nonuple +# cdef MPolynomial_libsingular pe +# for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): +# if i % n_proc == child_id: +# pe = feq_cy(factory,nonuple,prune=prune) +# if pe: +# factory.temp_eqns.append(reduce_poly_dict(pe.dict(),factory._nnz,factory._ks)) + +#################### +### Verification ### +#################### + +#Check the pentagon equation corresponding to the given nonuple +cpdef feq_verif(factory, tuple nonuple, float tol=5e-8): + a, b, c, d, e, f, g, k, l = nonuple + cdef float diff, lhs, rhs + lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) + rhs = 0.0 + for h in factory.FR.basis(): + rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) + diff = lhs - rhs + if diff > tol or diff < -tol: + factory.temp_eqns.append(diff) + +#Generate all the pentagon equations assigned to this process, and reduce them +from time import time +from os import getpid +cimport cython +@cython.wraparound(False) +@cython.nonecheck(False) +@cython.cdivision(True) +cpdef pent_verify(tuple mp_params, factory): + child_id, n_proc = mp_params + cdef float t0 + cdef tuple nonuple + cdef long i + t0 = time() + for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): + if i % n_proc == child_id: + feq_verif(factory,nonuple) + if i % 50000000 == 0 and i: + print("{:5d}m equations checked in {:8.2f}... {} misses so far...".format(i // 1000000,time()-t0,len(factory.temp_eqns))) + print("Ran through {} pentagons in {:8.2f}... Worker {} with {} reporting {} total misses...".format(i,time()-t0,child_id,getpid(),len(factory.temp_eqns))) + +################################ +### Well, this was a bust... ### +################################ + +#Given a tuple representation of an n-variate polynomial over a cyclotomic field, +#return a tuple representation of an (n+1)-variate polynomial over the rationals, +#with the cyclotomic field generator treated as an indeterminate and used as the +#last variable in the parent polynomial ring +cpdef tuple rationalify(factory, tuple eq_tup): + F = factory.FR.field() + cdef list rat_tup = list() + cdef ETuple exp + for exp, coeff in eq_tup: + #In the future we should avoid casting by ensuring all coeffs created are + #in fmats.FR.field (eg. cython reducers, FMatrix.fmat) + for cyc_power, rat_coeff in F(coeff).polynomial().dict().items(): + nnz = { len(exp) : cyc_power } + for idx, val in exp.sparse_iter(): + nnz[idx] = val + new_e = ETuple(nnz, len(exp)+1) + rat_tup.append((new_e, rat_coeff)) + return tuple(rat_tup) + +#Given a tuple representation of an n-variate polynomial over the rationals, +#return a tuple repn of the polynomial with integer coefficients, obtained by +#multiplying all coefficients by the least common multiple of denominators +# cpdef tuple integralify(tuple rat_tup): +# cdef int cdenom = LCM(coeff.denominator() for exp, coeff in rat_tup) +# cdef list int_tup = list() +# for exp, coeff in rat_tup: +# int_tup.append((exp, int(cdenom * coeff))) +# return tuple(int_tup) + +#Left inverse of rationalify... +#cylcotomify(factory, rationalify(factory, eq_tup)) == eq_tup +cpdef tuple cyclotomify(factory, tuple rat_tup): + gen = factory.FR.field().gen() + cdef cyc_dict = dict() + cdef ETuple exp + for exp, coeff in rat_tup: + cyc_pow = exp.get_exp(len(exp)-1) + new_e = ETuple({ pos : val for pos, val in exp.sparse_iter() if pos != len(exp)-1 }, len(exp)-1) + if new_e in cyc_dict: + cyc_dict[new_e] += gen ** cyc_pow * coeff + else: + cyc_dict[new_e] = gen ** cyc_pow * coeff + return tuple(cyc_dict.items()) + +# def clear_denom(eq): +# for var in eq.variables(): +# if int(str(var)[1:]) >= fmats._poly_ring.ngens(): +# d = eq.degree(var) +# eq *= var ** d +# return eq From e129b942d5245c7af8cf0b55e94d5995a3db7ff9 Mon Sep 17 00:00:00 2001 From: dwbmscz <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 19 Feb 2021 16:13:01 -0800 Subject: [PATCH 002/414] work on f_matrix.py --- src/sage/combinat/root_system/all.py | 1 + src/sage/combinat/root_system/f_matrix.py | 459 ++++++++++++++++++---- 2 files changed, 392 insertions(+), 68 deletions(-) diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index b8e06e4ff3d..7c620b9cac2 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -21,6 +21,7 @@ 'WeightRing']) lazy_import('sage.combinat.root_system.fusion_ring', ['FusionRing']) lazy_import('sage.combinat.root_system.f_matrix', ['FMatrix']) +lazy_import('sage.combinat.root_system.map_reduce_engine', ['MapReduceEngine']) from .branching_rules import BranchingRule, branching_rule_from_plethysm, branching_rule lazy_import('sage.combinat.root_system.non_symmetric_macdonald_polynomials', 'NonSymmetricMacdonaldPolynomials') diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 50261cd4583..b99aa1e2c3a 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -21,14 +21,18 @@ from itertools import product from sage.misc.misc import inject_variable + #Import pickle for checkpointing and loading certain variables try: import cPickle as pickle except: import pickle +from functools import cmp_to_key, partial from sage.rings.polynomial.polydict import ETuple -# from sage.rings import AlgebraicField as QQbar +from sage.combinat.root_system.poly_tup_engine import * +from sage.rings.qqbar import QQbar, number_field_elements_from_algebraics +import numpy as np class FMatrix(): r"""Return an F-Matrix factory for a FusionRing. @@ -230,9 +234,13 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.solved = set() #New attributes of the FMatrix class + self.field = self.FR.field() + r = self.field.defining_polynomial().roots(ring=QQbar,multiplicities=False)[0] + self._qqbar_embedding = self.field.hom([r],QQbar) self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } + self._ks = self.get_known_sq() + self._nnz = self.get_known_nonz() self._known_vals = dict() - self.field = self.FR.field() self.temp_eqns = [] ####################### @@ -474,13 +482,31 @@ def get_radical_expression(self): def get_known_vals(self): return { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in self.solved } + #Construct a dictionary of known squares. Keys are variable indices and corresponding values are the squares + def get_known_sq(self,eqns=None): + if eqns is None: + eqns = self.ideal_basis + return { variables(eq_tup)[0] : -eq_tup[-1][1] for eq_tup in eqns if tup_fixes_sq(eq_tup) } + #Construct an ETuple indicating positions of known nonzero variables. - #MUST be called after fmats._ks = get_known_sq() + #MUST be called after self._ks = get_known_sq() def get_known_nonz(self): nonz = { self._var_to_idx[var] : 100 for var in self._singles } for idx in self._ks: nonz[idx] = 100 - return ETuple(nonz, fmats._poly_ring.ngens()) + return ETuple(nonz, self._poly_ring.ngens()) + + ######################### + ### Useful predicates ### + ######################### + + #Determine if monomial exponent is univariate in a still unknown F-symbol + def is_univariate_in_unknown(self,monom_exp): + return len(monom_exp.nonzero_values()) == 1 and monom_exp.nonzero_positions()[0] not in self.solved + + #Determine if monomial exponent is univariate and linear in a vstill unknown F-symbol + def is_uni_linear_in_unkwown(self,monom_exp): + return monom_exp.nonzero_values() == [1] and monom_exp.nonzero_positions()[0] not in self.solved ############################## ### Variables partitioning ### @@ -515,13 +541,13 @@ def get_fr_str(self): def save_fvars(self,filename): with open(filename, 'wb') as f: pickle.dump(self._fvars, f) - # pickle.dump([fmats._fvars, fmats.solved], f) + # pickle.dump([self._fvars, self.solved], f) #If provided, optional param save_dir should have a trailing forward slash def load_fvars(self,save_dir=""): with open(save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle", 'rb') as f: self._fvars = pickle.load(f) - # fmats._fvars, fmats.solved = pickle.load(f) + # self._fvars, self.solved = pickle.load(f) ######################## ### Equations set up ### @@ -544,75 +570,114 @@ def get_orthogonality_constraints(self,output=True): return eqns self.ideal_basis.extend([self.poly_to_tup(eq) for eq in eqns]) - def feq(self, a, b, c, d, e, f, g, k, l, prune=False): - """ - Return True if the Pentagon axiom ([Bond2007]_ (2.77)) is satisfied. - """ - lhs = self.fmat(f,c,d,e,g,l)*self.fmat(a,b,l,e,f,k) - rhs = sum(self.fmat(a,b,c,g,f,h)*self.fmat(a,h,d,e,g,k)*self.fmat(b,c,d,k,h,l) for h in self.FR.basis()) - if lhs != 0 or not prune: # it is believed that if lhs=0, the equation carries no new information - return lhs - rhs - else: - return 0 - - def req(self, a, b, c, d, e, g, side="left"): - """ - Return A hexagon equation (Bond[2007]_ (2.78) or (2.79)). - - INPUT: + def get_hexagons(self,worker_pool=None,output=True): + he = mr_eng.emap_no_comm(hex_iter_setter,hex_caller,trivial_reducer_caller,worker_pool) + if output: + return [self.tup_to_fpoly(h) for h in he] + self.ideal_basis.extend(he) - - ``a,b,c,d,e,f`` -- basis elements of the FusionRing - - ``side`` -- (default left) which hexagon axiom to use + #If a worker_pool is passed, then we use multiprocessing + def get_pentagons(self,worker_pool=None,output=True): + pe = mr_eng.emap_no_comm(pent_iter_setter,pent_caller,trivial_reducer_caller,worker_pool) + if output: + return [self.tup_to_fpoly(p) for p in pe] + self.ideal_basis.extend(pe) - """ - if side == "left": - lhs = self.FR.r_matrix(a,c,e)*self.fmat(a,c,b,d,e,g)*self.FR.r_matrix(b,c,g) - rhs = sum(self.fmat(c,a,b,d,e,f)*self.FR.r_matrix(f,c,d)*self.fmat(a,b,c,d,f,g) for f in self.FR.basis()) - elif side == "right": - # r(a,b,x) is a root of unity, so its inverse is its complex conjugate - lhs = self.FR.r_matrix(c,a,e).conjugate()*self.fmat(a,c,b,d,e,g)*self.FR.r_matrix(c,b,g).conjugate() - rhs = sum(self.fmat(c,a,b,d,e,f)*self.FR.r_matrix(c,f,d).conjugate()*self.fmat(a,b,c,d,f,g) for f in self.FR.basis()) - return lhs-rhs - - def pentagon(self, verbose=False, output=True, factor=False, prune=False): - """ - Return generators of the ideal of Pentagon equations. + ############################ + ### Equations processing ### + ############################ - INPUT: + def tup_to_fpoly(eq_tup): + return tup_to_poly(eq_tup,parent=self._poly_ring) - - ``verbose`` -- (optional) set True for verbose. Default False - - ``output`` -- (optional) set True to output a set of equations. Default True - - ``factor`` -- (optional) set True for sreduce simplified equations. + #Solve for a linear term occurring in a two-term equation. + def solve_for_linear_terms(self,eqns=None): + if eqns is None: + eqns = self.ideal_basis - In contrast with the hexagon equations, where setting ``factor`` True - is a big improvement, for the pentagon equations this option produces - little or no simplification. So the default is False. + linear_terms_exist = False + for eq_tup in eqns: + if len(eq_tup) == 1: + m = eq_tup[0][0] + if self.is_univariate_in_unknown(m): + var = m.nonzero_positions()[0] + self._fvars[self._var_to_sextuple[self._poly_ring.gen(var)]] = tuple() + self.solved.add(var) + linear_terms_exist = True + if len(eq_tup) == 2: + monomials = [m for m, c in eq_tup] + max_var = monomials[0].emax(monomials[1]).nonzero_positions()[0] + for this, m in enumerate(monomials): + other = (this+1)%2 + if self.is_uni_linear_in_unkwown(m) and m.nonzero_positions()[0] == max_var and monomials[other][m.nonzero_positions()[0]] == 0: + var = m.nonzero_positions()[0] + rhs_key = monomials[other] + rhs_coeff = -eq_tup[other][1] / eq_tup[this][1] + self._fvars[self._var_to_sextuple[self._poly_ring.gen(var)]] = ((rhs_key,rhs_coeff),) + self.solved.add(var) + linear_terms_exist = True + return linear_terms_exist + + #Backward substitution step. Traverse variables in reverse lexicographical order. + def backward_subs(self): + for var in reversed(self._poly_ring.gens()): + sextuple = self._var_to_sextuple[var] + rhs = self._fvars[sextuple] + d = { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in variables(rhs) if var_idx in self.solved } + if d: + kp = compute_known_powers(get_variables_degrees([rhs]), d) + self._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp),self._ks).items()) + + def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): + if eqns is None: + eqns = self.ideal_basis + self._ks, self._var_degs = self.get_known_sq(eqns), get_variables_degrees(eqns) + self._nnz = self.get_known_nonz() + self._kp = compute_known_powers(self._var_degs,self.get_known_vals()) + if worker_pool is not None and children_need_update: + #self._nnz and self._kp are computed in child processes to reduce IPC overhead + n_proc = worker_pool._processes + new_data = [(self._fvars,self.solved,self._ks,self._var_degs)]*n_proc + mr_eng.map_triv_reduce(update_child_fmats,new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + + #Perform triangular elimination of linear terms in two-term equations until no such terms exist + #For optimal usage of TRIANGULAR elimination, pass in a SORTED list of equations + #Returns a list of equations + def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=True): + ret = True + if eqns is None: + eqns = self.ideal_basis + ret = False + if required_vars is None: + required_vars = self._poly_ring.gens() + poly_sortkey = cmp_to_key(poly_tup_cmp) + + #Testing new _fvars representation... Unzip polynomials + self._fvars = { sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items() } + + while True: + linear_terms_exist = self.solve_for_linear_terms(eqns) + if not linear_terms_exist: break + self.backward_subs() + + #Support early termination in case only some F-symbols are needed + req_vars_known = all(self._fvars[self._var_to_sextuple[var]] in self.FR.field() for var in required_vars) + if req_vars_known: return 1 + + #Compute new reduction params, send to child processes if any, and update eqns + # FIXME: replace 2500 below + self.update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>2500) + eqns = sorted(mr_eng.map_triv_reduce(update_reduce_caller,eqns,trivial_reducer_caller,worker_pool=worker_pool), key=poly_sortkey) + if verbose: + print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) - EXAMPLES:: + #Zip up _fvars before exiting + tzip = time() + self._fvars = { sextuple : self.tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items() } - sage: p = FMatrix(FusionRing("A2",1),fusion_label="c") - sage: p.pentagon()[-3:] - equations: 16 - [-fx5*fx6 + fx1, -fx4*fx6*fx7 + fx2, -fx5*fx7^2 + fx3*fx6] - sage: p.pentagon(factor=True)[-3:] - equations: 16 - [fx5*fx6 - fx1, fx4*fx6*fx7 - fx2, fx5*fx7^2 - fx3*fx6] - - """ - if output: - ret = [] - for (a,b,c,d,e,f,g,k,l) in list(product(self.FR.basis(), repeat=9)): - pd = self.feq(a,b,c,d,e,f,g,k,l,prune=prune) - if pd != 0: - if factor: - pd = self.sreduce(pd) - if output: - ret.append(pd) - if verbose: - print ("%s,%s,%s,%s,%s,%s,%s,%s,%s : %s"%(a,b,c,d,e,f,g,k,l,pd)) - print ("equations: %s"%len(ret)) - if output: - return ret + if ret: + return eqns + self.ideal_basis = eqns ##################### ### Graph methods ### @@ -657,10 +722,244 @@ def equations_graph(self,eqns=None): G.add_edge(x,y) return G + #Partition equations corresponding to edges in a disconnected graph + def partition_eqns(graph,eqns=None,verbose=True): + if eqns is None: + eqns = self.ideal_basis + partition = { tuple(c) : [] for c in graph.connected_components() } + for eq_tup in eqns: + partition[tuple(graph.connected_component_containing_vertex(variables(eq_tup)[0]))].append(eq_tup) + if verbose: + print("Partitioned {} equations into {} components of size:".format(len(eqns),len(graph.connected_components()))) + print(graph.connected_components_sizes()) + return partition + + #Compute a Groebner basis for a set of equations partitioned according to their corresponding graph + def par_graph_gb(worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): + if eqns is None: eqns = self.ideal_basis + graph = self.equations_graph(eqns) + small_comps = list() + + #For informative print statement + nmax = largest_fmat_size() + vars_by_size = list() + for i in range(nmax+1): + vars_by_size.append(self.get_fmats_by_size(i)) + + for comp, comp_eqns in self.partition_eqns(graph,verbose=verbose).items(): + #Check if component is too large to process + if len(comp) > 60: + fmat_size = 0 + #For informative print statement + for i in range(1,nmax+1): + if set(comp).issubset(vars_by_size[i]): + fmat_size = i + print("Component of size {} with vars in F-mats of size {} is too large to find GB".format(len(comp),fmat_size)) + self.temp_eqns.extend(comp_eqns) + else: + small_comps.append(comp_eqns) + gb_calculator = partial(compute_gb,term_order=term_order) + small_comp_gb = mr_eng.map_triv_reduce(gb_calculator,small_comps,trivial_reducer_caller,worker_pool=worker_pool,chunksize=1,mp_thresh=50) + ret = small_comp_gb + self.temp_eqns + self.temp_eqns = [] + return ret + + #Translate equations in each connected component to smaller polynomial rings + #so we can call built-in variety method. + def get_component_variety(var, eqns): + #Define smaller poly ring in component vars + R = PolynomialRing(self.FR.field(),len(var),'a',order='lex') + + #Zip tuples into R and compute Groebner basis + idx_map = { old : new for new, old in enumerate(sorted(var)) } + nvars = len(var) + polys = [tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R) for eq_tup in eqns] + var_in_R = Ideal(sorted(polys)).variety(ring=AA) + + #Change back to fmats poly ring and append to temp_eqns + inv_idx_map = { v : k for k, v in idx_map.items() } + return [{ inv_idx_map[i] : value for i, (key, value) in enumerate(sorted(soln.items())) } for soln in var_in_R] + + ############### + ### Mappers ### + ############### + + #Map callers are called in the worker processes. Map callers are used to supply + #the worker's local copy of forked variables to the relevant function + + #Set the required input iterable for consumption + def hex_iter_setter(proc): + mr_eng.input_iter = product(self.FR.basis(),repeat=6) + + def pent_iter_setter(proc): + mr_eng.input_iter = product(self.FR.basis(),repeat=9) + + #These callers should be cythonized + def hex_caller(mp_params): + mr_eng.map_caller(mp_params,req_cy,(fmats,)) + + def pent_caller(mp_params): + mr_eng.map_caller(mp_params,feq_cy,(fmats,)) + + #Substitute known values, known squares, and reduce a given equation + def update_reduce_caller(eq_tup): + update_reduce(eq_tup,fmats,mr_eng) + + #Compute Groebner basis for given equations iterable + def compute_gb(eqns,term_order="degrevlex"): + #Define smaller poly ring in component vars + sorted_vars = sorted(set(fx for eq_tup in eqns for fx in variables(eq_tup))) + R = PolynomialRing(self.FR.field(),len(sorted_vars),'a',order=term_order) + + #Zip tuples into R and compute Groebner basis + idx_map = { old : new for new, old in enumerate(sorted_vars) } + nvars = len(sorted_vars) + polys = [tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R) for eq_tup in eqns] + gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") + + #Change back to fmats poly ring and append to temp_eqns + inv_idx_map = { v : k for k, v in idx_map.items() } + nvars = self._poly_ring.ngens() + for p in gb: + mr_eng.worker_results.append(resize(poly_to_tup(p),inv_idx_map,nvars)) + + #One-to-all communication used to update fvars after triangular elim step. + def update_child_fmats(data_tup): + #fmats is assumed to be global before forking used to create the Pool object, + #so each child has a global fmats variable. So it's enough to update that object + self._fvars, self.solved, self._ks, self._var_degs = data_tup + self._nnz = self.get_known_nonz() + self._kp = compute_known_powers(self._var_degs,self.get_known_vals()) + + ################ + ### Reducers ### + ################ + + #Trivial reducer: simply collects objects with the same key in the worker + def trivial_reducer_caller(proc): + return mr_eng.reduce_single_proc(0) + ####################### ### Solution method ### ####################### + #Get a numeric solution for given equations by using the partitioning the equations + #graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, + #which is computed as a Cartesian product + def get_numeric_solution(eqns=None,verbose=True): + if eqns is None: + eqns = self.ideal_basis + eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) + + x = self.FR.field()['x'].gen() + numeric_fvars = dict() + non_cyclotomic_roots = list() + must_change_base_field = False + phi = self.FR.field().hom([self.field.gen()],self.FR.field()) + for comp, part in eqns_partition.items(): + #If component have only one equation in a single variable, get a root + if len(comp) == 1 and len(part) == 1: + #Attempt to find cyclotomic root + univ_poly = tup_to_univ_poly(part[0],x) + real_roots = univ_poly.roots(ring=AA,multiplicities=False) + assert real_roots, "No real solution exists... {} has no real roots".format(univ_poly) + roots = univ_poly.roots(multiplicities=False) + if roots: + numeric_fvars[comp[0]] = roots[0] + else: + non_cyclotomic_roots.append((comp[0],real_roots[0])) + must_change_base_field = True + #Otherwise, compute the component variety and select a point to obtain a numerical solution + else: + sols = self.get_component_variety(comp,part) + assert len(sols) > 1, "No real solution exists... component with variables {} has no real points".format(comp) + for fx, rhs in sols[0].items(): + non_cyclotomic_roots.append((fx,rhs)) + must_change_base_field = True + + if must_change_base_field: + #If needed, find a NumberField containing all the roots + roots = [self.FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] + self.field, nf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots,minimal=True) + #Embed cyclotomic field into newly constructed NumberField + cyc_gen_as_nf_elt = nf_elts.pop(0) + phi = self.FR.field().hom([cyc_gen_as_nf_elt], self.field) + numeric_fvars = { k : phi(v) for k, v in numeric_fvars.items() } + for i, elt in enumerate(nf_elts): + numeric_fvars[non_cyclotomic_roots[i][0]] = elt + + #Do some appropriate conversions + new_poly_ring = self._poly_ring.change_ring(self.field) + nvars = self._poly_ring.ngens() + self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } + self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } + self._poly_ring = new_poly_ring + self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } + + #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) + self.solved.update(numeric_fvars) + nvars = self._poly_ring.ngens() + for fx, rhs in numeric_fvars.items(): + self._fvars[self._var_to_sextuple[self._poly_ring.gen(fx)]] = ((ETuple({},nvars),rhs),) + assert len(self.solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self.solved)]) + self.clear_equations() + backward_subs() + self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } + + #Solver + #If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash + #Supports "warm" start. Use load_fvars to re-start computation from checkpoint + def find_real_unitary_solution(use_mp=True,save_dir="",verbose=True): + #Set multiprocessing parameters. Context can only be set once, so we try to set it + try: + set_start_method('fork') + except RuntimeError: + pass + pool = Pool(processes=max(cpu_count()-1,1)) if use_mp else None + print("Computing F-symbols for {} with {} variables...".format(FR, len(self._fvars))) + + #Set up hexagon equations and orthogonality constraints + self.get_orthogonality_constraints(output=False) + self.get_hexagons(worker_pool=pool,output=False) + self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) + if verbose: + print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) + + #Set up equations graph. Find GB for each component in parallel. Eliminate variables + self.ideal_basis = sorted(self.par_graph_gb(worker_pool=pool), key=poly_sortkey) + print("GB is of length",len(self.ideal_basis)) + self.triangular_elim(worker_pool=pool,verbose=verbose,mp_thresh=mp_thresh) + + #Report progress and checkpoint! + if verbose: + print("Hex elim step solved for {} / {} variables".format(len(self.solved), len(self._poly_ring.gens()))) + filename = save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle" + self.save_fvars(filename) + + #Update reduction parameters, also in children if any + self.update_reduction_params(worker_pool=pool,children_need_update=True) + + #Set up pentagon equations in parallel, simplify, and eliminate variables + self.get_pentagons(worker_pool=pool,output=False) + if verbose: + print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) + self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) + self.triangular_elim(worker_pool=pool,verbose=verbose,mp_thresh=mp_thresh) + + #Report progress and checkpoint! + if verbose: + print("Pent elim step solved for {} / {} variables".format(len(self.solved), len(self._poly_ring.gens()))) + self.save_fvars(filename) + + #Close worker pool to free resources + if pool is not None: pool.close() + + #Set up new equations graph and compute variety for each component + self.ideal_basis = sorted(self.par_graph_gb(term_order="lex"), key=poly_sortkey) + self.triangular_elim(verbose=verbose) + self.get_numeric_solution(verbose=verbose) + self.save_fvars(filename) + def get_solution(self, equations=None, factor=True, verbose=True, prune=True, algorithm='', output=False): """ Solve the the hexagon and pentagon relations to evaluate the F-matrix. @@ -744,6 +1043,30 @@ def verify_hexagons(self): hex.append(lhs-rhs) return list(set(hex)) + #Some abstract nonsense to ensure the pentagon verifier is called with the fmats + #object living in each child process + def pent_verify_caller(mp_params): + pent_verify(mp_params, fmats) + + def par_pent_verify(use_mp=True,prune=False): + print("Testing F-symbols for {}".format(self.FR)) + treal = time() + self._fvars = { sextuple : float(RDF(rhs)) for sextuple, rhs in self.get_fmats_in_alg_field().items() } + print("Converted to floats in {}".format(time()-treal)) + if use_mp: + pool = Pool(processes=cpu_count()) + else: + pool = None + n_proc = pool._processes if pool is not None else 1 + params = [(child_id,n_proc) for child_id in range(n_proc)] + pe = mr_eng.map_triv_reduce(pent_verify_caller,params,trivial_reducer_caller,worker_pool=pool,chunksize=1,mp_thresh=0) + if np.all(np.isclose(np.array(pe),0)): + print("Success!!! Found valid F-symbols for {}".format(self.FR)) + else: + print("Ooops... something went wrong... These pentagons remain:") + print(pe) + return pe + #Verify that all F-matrices are real and unitary (orthogonal) def fmats_are_orthogonal(self): is_orthog = [] From 2963d485ce1459372da08d9b48b0d38f08560343 Mon Sep 17 00:00:00 2001 From: dwbmscz <75940445+dwbmscz@users.noreply.github.com> Date: Wed, 24 Feb 2021 13:38:51 -0800 Subject: [PATCH 003/414] now working to compute F-matrices --- src/sage/combinat/root_system/f_matrix.py | 393 ++++++++---------- .../fast_parallel_fmats_methods.pxd | 5 + .../fast_parallel_fmats_methods.pyx | 153 +++++++ src/sage/combinat/root_system/fusion_ring.py | 228 ++++++++-- .../combinat/root_system/poly_tup_engine.pxd | 12 +- .../combinat/root_system/poly_tup_engine.pyx | 165 +------- 6 files changed, 534 insertions(+), 422 deletions(-) create mode 100644 src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd create mode 100644 src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index b99aa1e2c3a..862cfc3514e 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -11,28 +11,29 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.misc.misc import inject_variable -from sage.matrix.constructor import matrix -from sage.rings.polynomial.all import PolynomialRing -from sage.rings.ideal import Ideal -from sage.combinat.root_system.fusion_ring import FusionRing +from functools import cmp_to_key, partial +from itertools import product +import sage.combinat.root_system.fusion_ring as FusionRing import sage.graphs from sage.graphs.generators.basic import EmptyGraph -from itertools import product +from sage.matrix.constructor import matrix from sage.misc.misc import inject_variable +from sage.rings.polynomial.all import PolynomialRing +from sage.rings.ideal import Ideal - +from copy import deepcopy #Import pickle for checkpointing and loading certain variables try: import cPickle as pickle except: import pickle - -from functools import cmp_to_key, partial -from sage.rings.polynomial.polydict import ETuple -from sage.combinat.root_system.poly_tup_engine import * -from sage.rings.qqbar import QQbar, number_field_elements_from_algebraics +from multiprocessing import cpu_count, Pool, set_start_method import numpy as np +from sage.combinat.root_system.fast_parallel_fmats_methods import * +from sage.combinat.root_system.poly_tup_engine import * +from sage.rings.polynomial.polydict import ETuple +from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics +from sage.rings.real_double import RDF class FMatrix(): r"""Return an F-Matrix factory for a FusionRing. @@ -111,12 +112,14 @@ class FMatrix(): Due to the large number of equations we may fail to find a Groebner basis if there are too many variables. + EXAMPLES:: sage: I=FusionRing("E8",2,conjugate=True) sage: I.fusion_labels(["i0","p","s"],inject_variables=True) sage: f = FMatrix(I,inject_variables=True); f creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients We've exported two sets of variables to the global namespace. @@ -165,9 +168,10 @@ class FMatrix(): EXAMPLES:: - sage: f.pentagon()[1:3] - equations: 41 - [-fx0*fx1 + fx1, -fx1*fx2^2 + fx1] + sage: f.get_pentagons()[1:3] + [fx1*fx5 - fx7^2, fx5*fx8*fx13 - fx2*fx12] + + sage: f.hexagon()[1:3] equations: 14 [fx1*fx5 + fx2, fx2 + 1] @@ -217,13 +221,14 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.FR = fusion_ring if self.FR._fusion_labels is None: self.FR.fusion_labels(fusion_label, inject_variables=True) - #Set up F-symbols entry by entry + #Set up F-symbols entry by entry n_vars = self.findcases() self._poly_ring = PolynomialRing(self.FR.field(),n_vars,var_prefix) if inject_variables: print ("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) - for i in range(self._poly_ring.ngens()): - inject_variable("%s%s"%(var_prefix,i),self._poly_ring.gens()[i]) + self._poly_ring.inject_variables() + # for i in range(self._poly_ring.ngens()): + # inject_variable("%s%s"%(var_prefix,i),self._poly_ring.gens()[i]) self._var_to_sextuple, self._fvars = self.findcases(output=True) self._singles = self.singletons() @@ -241,7 +246,11 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._ks = self.get_known_sq() self._nnz = self.get_known_nonz() self._known_vals = dict() - self.temp_eqns = [] + self.symbols_known = False + + #Multiprocessing attributes + self.mp_thresh = 7500 + ####################### ### Class utilities ### @@ -520,6 +529,7 @@ def largest_fmat_size(self): #they belong to def get_fmats_by_size(self,n): fvars_copy = deepcopy(self._fvars) + solved_copy = deepcopy(self.solved) self.clear_vars() var_set = set() for quadruple in product(self.FR.basis(),repeat=4): @@ -528,6 +538,7 @@ def get_fmats_by_size(self,n): if F.nrows() == n and F.coefficients() != [1]: var_set.update(self._var_to_idx[fx] for fx in F.coefficients()) self._fvars = fvars_copy + self.solved = solved_copy return var_set ############################ @@ -540,27 +551,98 @@ def get_fr_str(self): def save_fvars(self,filename): with open(filename, 'wb') as f: - pickle.dump(self._fvars, f) - # pickle.dump([self._fvars, self.solved], f) + pickle.dump([self._fvars, self.solved], f) #If provided, optional param save_dir should have a trailing forward slash def load_fvars(self,save_dir=""): with open(save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle", 'rb') as f: - self._fvars = pickle.load(f) - # self._fvars, self.solved = pickle.load(f) + self._fvars, self.solved = pickle.load(f) + + ################# + ### MapReduce ### + ################# + + #Map fn across input_iter and reduce output to polynomial + #If worker_pool is not provided, function maps and reduces on a single process. + #If worker_pool is provided, the function attempts to determine whether it should + #use multiprocessing based on the length of the input iterable. If it can't determine + #the length of the input iterable then it uses multiprocessing with the default chunksize of 1 + #if chunksize is not explicitly provided. + def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): + if mp_thresh is None: + mp_thresh = self.mp_thresh + #Compute multiprocessing parameters + if worker_pool is not None: + try: + n = len(input_iter) + except: + n = mp_thresh + 1 + if chunksize is None: + chunksize = n // (worker_pool._processes**2) + 1 + no_mp = worker_pool is None or n < mp_thresh + #Map phase. Casting Async Object blocks execution... Each process holds results + #in its copy of fmats.temp_eqns + if no_mp: + list(map(mapper,input_iter)) + else: + list(worker_pool.imap_unordered(mapper,input_iter,chunksize=chunksize)) + #Reduce phase + if no_mp: + results = collect_eqns(0) + else: + results = self.reduce_multi_process(collect_eqns,worker_pool) + return results + + ###################### + ### Mapper callers ### + ###################### + + #Map callers are called in the worker processes. Map callers are used to supply + #the worker's local copy of forked variables to the relevant function + + #Substitute known values, known squares, and reduce a given equation + def update_reduce_caller(self,eq_tup): + update_reduce(self,eq_tup) + + #Compute Groebner basis for ideal defined by the given equations + def compute_gb_caller(self,eqns,term_order="degrevlex"): + compute_gb(self,eqns,term_order) + + #Construct reduced hexagons in a worker using the worker's copy of fmats + def reduced_hex_caller(self,mp_params): + get_reduced_hexagons(self,*mp_params) + + #Construct reduced pentagons in a worker using the worker's copy of fmats + def reduced_pent_caller(self,mp_params,prune=True): + get_reduced_pentagons(self,*mp_params) + + #One-to-all communication used to update fvars after triangular elim step. + def update_child_fmats(self,data_tup): + #fmats is assumed to be global before forking used to create the Pool object, + #so each child has a global fmats variable. So it's enough to update that object + self._fvars, self.solved, self._ks, self._var_degs = data_tup + self._nnz = self.get_known_nonz() + self._kp = compute_known_powers(self._var_degs,self.get_known_vals()) + + #Verify satisfaction of pentagon equations + def pent_verify_caller(self,mp_params): + pent_verify(mp_params,self) + + ################ + ### Reducers ### + ################ + + #All-to-one communication step + def reduce_multi_process(self,reducer,worker_pool): + collected_eqns = set() + for child_eqns in worker_pool.imap_unordered(reducer,range(worker_pool._processes)): + collected_eqns.update(child_eqns) + return list(collected_eqns) ######################## ### Equations set up ### ######################## - def poly_to_tup(self,poly): - return tuple(poly.dict().items()) - - def tup_to_poly(self,eq_tup,parent=None): - if parent is None: - parent = self._poly_ring - return parent({ exp : coeff for exp, coeff in eq_tup }) - def get_orthogonality_constraints(self,output=True): eqns = list() for tup in product(self.FR.basis(), repeat=4): @@ -568,26 +650,32 @@ def get_orthogonality_constraints(self,output=True): eqns.extend((mat.T * mat - matrix.identity(mat.nrows())).coefficients()) if output: return eqns - self.ideal_basis.extend([self.poly_to_tup(eq) for eq in eqns]) + self.ideal_basis.extend([poly_to_tup(eq) for eq in eqns]) + #If a worker_pool is passed, then we use multiprocessing def get_hexagons(self,worker_pool=None,output=True): - he = mr_eng.emap_no_comm(hex_iter_setter,hex_caller,trivial_reducer_caller,worker_pool) + n_proc = worker_pool._processes if worker_pool is not None else 1 + params = [(child_id, n_proc) for child_id in range(n_proc)] + he = self.map_triv_reduce(self.reduced_hex_caller,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) if output: return [self.tup_to_fpoly(h) for h in he] self.ideal_basis.extend(he) #If a worker_pool is passed, then we use multiprocessing def get_pentagons(self,worker_pool=None,output=True): - pe = mr_eng.emap_no_comm(pent_iter_setter,pent_caller,trivial_reducer_caller,worker_pool) + n_proc = worker_pool._processes if worker_pool is not None else 1 + params = [(child_id, n_proc) for child_id in range(n_proc)] + pe = self.map_triv_reduce(self.reduced_pent_caller,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) if output: - return [self.tup_to_fpoly(p) for p in pe] + return [self.tup_to_fpoly(h) for h in pe] self.ideal_basis.extend(pe) ############################ ### Equations processing ### ############################ - def tup_to_fpoly(eq_tup): + #Assemble a polynomial object from its tuple representation + def tup_to_fpoly(self,eq_tup): return tup_to_poly(eq_tup,parent=self._poly_ring) #Solve for a linear term occurring in a two-term equation. @@ -628,6 +716,7 @@ def backward_subs(self): kp = compute_known_powers(get_variables_degrees([rhs]), d) self._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp),self._ks).items()) + #Update reduction parameters in all processes def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): if eqns is None: eqns = self.ideal_basis @@ -638,7 +727,7 @@ def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes new_data = [(self._fvars,self.solved,self._ks,self._var_degs)]*n_proc - mr_eng.map_triv_reduce(update_child_fmats,new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + self.map_triv_reduce(self.update_child_fmats,new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) #Perform triangular elimination of linear terms in two-term equations until no such terms exist #For optimal usage of TRIANGULAR elimination, pass in a SORTED list of equations @@ -665,16 +754,13 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T if req_vars_known: return 1 #Compute new reduction params, send to child processes if any, and update eqns - # FIXME: replace 2500 below - self.update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>2500) - eqns = sorted(mr_eng.map_triv_reduce(update_reduce_caller,eqns,trivial_reducer_caller,worker_pool=worker_pool), key=poly_sortkey) + self.update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) + eqns = sorted(self.map_triv_reduce(self.update_reduce_caller,eqns,worker_pool=worker_pool), key=poly_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) #Zip up _fvars before exiting - tzip = time() self._fvars = { sextuple : self.tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items() } - if ret: return eqns self.ideal_basis = eqns @@ -684,31 +770,6 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T ##################### def equations_graph(self,eqns=None): - """ - Return a graph whose vertices are variables - and whose edges correspond to equations - relating two variables. - - This graph may contain isolated vertices... - - INPUT: - - - ``equations`` -- a list of equations - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("A1",3)) - sage: G = f.equation_graph(f.hexagon(factor=True)) - equations: 71 - sage: G.connected_components_number() - 14 - sage: G1=G.connected_components_subgraphs()[0] - sage: G1.size() - 60 - sage: G1.is_regular() - True - - """ if eqns is None: eqns = self.ideal_basis @@ -723,7 +784,7 @@ def equations_graph(self,eqns=None): return G #Partition equations corresponding to edges in a disconnected graph - def partition_eqns(graph,eqns=None,verbose=True): + def partition_eqns(self,graph,eqns=None,verbose=True): if eqns is None: eqns = self.ideal_basis partition = { tuple(c) : [] for c in graph.connected_components() } @@ -735,13 +796,14 @@ def partition_eqns(graph,eqns=None,verbose=True): return partition #Compute a Groebner basis for a set of equations partitioned according to their corresponding graph - def par_graph_gb(worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): + def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): if eqns is None: eqns = self.ideal_basis graph = self.equations_graph(eqns) small_comps = list() + temp_eqns = list() #For informative print statement - nmax = largest_fmat_size() + nmax = self.largest_fmat_size() vars_by_size = list() for i in range(nmax+1): vars_by_size.append(self.get_fmats_by_size(i)) @@ -755,18 +817,17 @@ def par_graph_gb(worker_pool=None,eqns=None,term_order="degrevlex",verbose=True) if set(comp).issubset(vars_by_size[i]): fmat_size = i print("Component of size {} with vars in F-mats of size {} is too large to find GB".format(len(comp),fmat_size)) - self.temp_eqns.extend(comp_eqns) + temp_eqns.extend(comp_eqns) else: small_comps.append(comp_eqns) - gb_calculator = partial(compute_gb,term_order=term_order) - small_comp_gb = mr_eng.map_triv_reduce(gb_calculator,small_comps,trivial_reducer_caller,worker_pool=worker_pool,chunksize=1,mp_thresh=50) - ret = small_comp_gb + self.temp_eqns - self.temp_eqns = [] + gb_calculator = partial(self.compute_gb_caller,term_order=term_order) + small_comp_gb = self.map_triv_reduce(gb_calculator,small_comps,worker_pool=worker_pool,chunksize=1,mp_thresh=50) + ret = small_comp_gb + temp_eqns return ret #Translate equations in each connected component to smaller polynomial rings #so we can call built-in variety method. - def get_component_variety(var, eqns): + def get_component_variety(self,var,eqns): #Define smaller poly ring in component vars R = PolynomialRing(self.FR.field(),len(var),'a',order='lex') @@ -780,65 +841,6 @@ def get_component_variety(var, eqns): inv_idx_map = { v : k for k, v in idx_map.items() } return [{ inv_idx_map[i] : value for i, (key, value) in enumerate(sorted(soln.items())) } for soln in var_in_R] - ############### - ### Mappers ### - ############### - - #Map callers are called in the worker processes. Map callers are used to supply - #the worker's local copy of forked variables to the relevant function - - #Set the required input iterable for consumption - def hex_iter_setter(proc): - mr_eng.input_iter = product(self.FR.basis(),repeat=6) - - def pent_iter_setter(proc): - mr_eng.input_iter = product(self.FR.basis(),repeat=9) - - #These callers should be cythonized - def hex_caller(mp_params): - mr_eng.map_caller(mp_params,req_cy,(fmats,)) - - def pent_caller(mp_params): - mr_eng.map_caller(mp_params,feq_cy,(fmats,)) - - #Substitute known values, known squares, and reduce a given equation - def update_reduce_caller(eq_tup): - update_reduce(eq_tup,fmats,mr_eng) - - #Compute Groebner basis for given equations iterable - def compute_gb(eqns,term_order="degrevlex"): - #Define smaller poly ring in component vars - sorted_vars = sorted(set(fx for eq_tup in eqns for fx in variables(eq_tup))) - R = PolynomialRing(self.FR.field(),len(sorted_vars),'a',order=term_order) - - #Zip tuples into R and compute Groebner basis - idx_map = { old : new for new, old in enumerate(sorted_vars) } - nvars = len(sorted_vars) - polys = [tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R) for eq_tup in eqns] - gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") - - #Change back to fmats poly ring and append to temp_eqns - inv_idx_map = { v : k for k, v in idx_map.items() } - nvars = self._poly_ring.ngens() - for p in gb: - mr_eng.worker_results.append(resize(poly_to_tup(p),inv_idx_map,nvars)) - - #One-to-all communication used to update fvars after triangular elim step. - def update_child_fmats(data_tup): - #fmats is assumed to be global before forking used to create the Pool object, - #so each child has a global fmats variable. So it's enough to update that object - self._fvars, self.solved, self._ks, self._var_degs = data_tup - self._nnz = self.get_known_nonz() - self._kp = compute_known_powers(self._var_degs,self.get_known_vals()) - - ################ - ### Reducers ### - ################ - - #Trivial reducer: simply collects objects with the same key in the worker - def trivial_reducer_caller(proc): - return mr_eng.reduce_single_proc(0) - ####################### ### Solution method ### ####################### @@ -846,7 +848,7 @@ def trivial_reducer_caller(proc): #Get a numeric solution for given equations by using the partitioning the equations #graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, #which is computed as a Cartesian product - def get_numeric_solution(eqns=None,verbose=True): + def get_numeric_solution(self,eqns=None,verbose=True): if eqns is None: eqns = self.ideal_basis eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) @@ -894,31 +896,34 @@ def get_numeric_solution(eqns=None,verbose=True): self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } self._poly_ring = new_poly_ring - self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } - #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) + #Ensure all F-symbols are known self.solved.update(numeric_fvars) nvars = self._poly_ring.ngens() + assert len(self.solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self.solved)]) + + #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) + self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } for fx, rhs in numeric_fvars.items(): self._fvars[self._var_to_sextuple[self._poly_ring.gen(fx)]] = ((ETuple({},nvars),rhs),) - assert len(self.solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self.solved)]) - self.clear_equations() - backward_subs() + self.backward_subs() self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } + self.clear_equations() #Solver #If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash #Supports "warm" start. Use load_fvars to re-start computation from checkpoint - def find_real_unitary_solution(use_mp=True,save_dir="",verbose=True): + def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): #Set multiprocessing parameters. Context can only be set once, so we try to set it try: set_start_method('fork') except RuntimeError: pass pool = Pool(processes=max(cpu_count()-1,1)) if use_mp else None - print("Computing F-symbols for {} with {} variables...".format(FR, len(self._fvars))) + print("Computing F-symbols for {} with {} variables...".format(self.FR, len(self._fvars))) #Set up hexagon equations and orthogonality constraints + poly_sortkey = cmp_to_key(poly_tup_cmp) self.get_orthogonality_constraints(output=False) self.get_hexagons(worker_pool=pool,output=False) self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) @@ -928,7 +933,7 @@ def find_real_unitary_solution(use_mp=True,save_dir="",verbose=True): #Set up equations graph. Find GB for each component in parallel. Eliminate variables self.ideal_basis = sorted(self.par_graph_gb(worker_pool=pool), key=poly_sortkey) print("GB is of length",len(self.ideal_basis)) - self.triangular_elim(worker_pool=pool,verbose=verbose,mp_thresh=mp_thresh) + self.triangular_elim(worker_pool=pool,verbose=verbose) #Report progress and checkpoint! if verbose: @@ -944,7 +949,7 @@ def find_real_unitary_solution(use_mp=True,save_dir="",verbose=True): if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) - self.triangular_elim(worker_pool=pool,verbose=verbose,mp_thresh=mp_thresh) + self.triangular_elim(worker_pool=pool,verbose=verbose) #Report progress and checkpoint! if verbose: @@ -959,76 +964,7 @@ def find_real_unitary_solution(use_mp=True,save_dir="",verbose=True): self.triangular_elim(verbose=verbose) self.get_numeric_solution(verbose=verbose) self.save_fvars(filename) - - def get_solution(self, equations=None, factor=True, verbose=True, prune=True, algorithm='', output=False): - """ - Solve the the hexagon and pentagon relations to evaluate the F-matrix. - - INPUT: - - - ``equations`` -- (optional) a set of equations to be - solved. Defaults to the hexagon and pentagon equations. - - ``factor`` -- (default: ``True``). Set true to use - the sreduce method to simplify the hexagon and pentagon - equations before solving them. - - ``algorithm`` -- (optional). Algorithm to compute Groebner Basis. - - ``output`` -- (optional, default False). Output a dictionary of - F-matrix values. This may be useful to see but may be omitted - since this information will be available afterwards via the - :meth:`fmatrix` and :meth:`fmat` methods. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("A2",1),fusion_label="a") - sage: f.get_solution(verbose=False,output=True) - equations: 8 - equations: 16 - adding equation... fx4 - 1 - {(a2, a2, a2, a0, a1, a1): 1, - (a2, a2, a1, a2, a1, a0): 1, - (a2, a1, a2, a2, a0, a0): 1, - (a2, a1, a1, a1, a0, a2): 1, - (a1, a2, a2, a2, a0, a1): 1, - (a1, a2, a1, a1, a0, a0): 1, - (a1, a1, a2, a1, a2, a0): 1, - (a1, a1, a1, a0, a2, a2): 1} - - After you successfully run ``get_solution`` you may check - the correctness of the F-matrix by running :meth:`hexagon` - and :meth:`pentagon`. These should return empty lists - of equations. In this example, we turn off the factor - and prune optimizations to test all instances. - - EXAMPLES:: - - sage: f.hexagon(factor=False) - equations: 0 - [] - sage: f.hexagon(factor=False,side="right") - equations: 0 - [] - sage: f.pentagon(factor=False,prune=False) - equations: 0 - [] - - """ - if equations is None: - if verbose: - print("Setting up hexagons and pentagons...") - equations = self.hexagon(verbose=False, factor=factor)+self.pentagon(verbose=False, factor=factor, prune=prune) - if verbose: - print("Finding a Groebner basis...") - self.ideal_basis = set(Ideal(equations).groebner_basis(algorithm=algorithm)) - if verbose: - print("Solving...") - self.substitute_degree_one() - if verbose: - print("Fixing the gauge...") - self.fix_gauge(algorithm=algorithm) - if verbose: - print("Done!") - if output: - return self._fvars + self.symbols_known = True ##################### ### Verifications ### @@ -1041,31 +977,32 @@ def verify_hexagons(self): lhs = self.field(self.FR.r_matrix(a,c,e))*self.fmat(a,c,b,d,e,g)*self.field(self.FR.r_matrix(b,c,g)) rhs = sum(self.fmat(c,a,b,d,e,f)*self.field(self.FR.r_matrix(f,c,d))*self.fmat(a,b,c,d,f,g) for f in self.FR.basis()) hex.append(lhs-rhs) - return list(set(hex)) - - #Some abstract nonsense to ensure the pentagon verifier is called with the fmats - #object living in each child process - def pent_verify_caller(mp_params): - pent_verify(mp_params, fmats) + if all(h == self.field.zero() for h in hex): + print("Success!!! Found valid F-symbols for {}".format(self.FR)) + else: + print("Ooops... something went wrong... These pentagons remain:") + print(hex) + return hex - def par_pent_verify(use_mp=True,prune=False): - print("Testing F-symbols for {}".format(self.FR)) - treal = time() + def verify_pentagons(self,use_mp=True,prune=False): + print("Testing F-symbols for {}...".format(self.FR)) + fvars_copy = deepcopy(self._fvars) self._fvars = { sextuple : float(RDF(rhs)) for sextuple, rhs in self.get_fmats_in_alg_field().items() } - print("Converted to floats in {}".format(time()-treal)) if use_mp: pool = Pool(processes=cpu_count()) else: pool = None n_proc = pool._processes if pool is not None else 1 params = [(child_id,n_proc) for child_id in range(n_proc)] - pe = mr_eng.map_triv_reduce(pent_verify_caller,params,trivial_reducer_caller,worker_pool=pool,chunksize=1,mp_thresh=0) - if np.all(np.isclose(np.array(pe),0)): + pe = self.map_triv_reduce(self.pent_verify_caller,params,worker_pool=pool,chunksize=1,mp_thresh=0) + if np.all(np.isclose(np.array(pe),0,atol=1e-7)): print("Success!!! Found valid F-symbols for {}".format(self.FR)) + pe = None else: print("Ooops... something went wrong... These pentagons remain:") print(pe) - return pe + self._fvars = fvars_copy + return pe #Verify that all F-matrices are real and unitary (orthogonal) def fmats_are_orthogonal(self): diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd new file mode 100644 index 00000000000..bcebff6c376 --- /dev/null +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -0,0 +1,5 @@ +cpdef get_reduced_hexagons(factory, int child_id, int n_proc) +cpdef get_reduced_pentagons(factory, int child_id, int n_proc, bint prune=*) +cpdef update_reduce(factory, tuple eq_tup) +cpdef compute_gb(factory,eqns,term_order=*) +cpdef pent_verify(tuple mp_params, factory) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx new file mode 100644 index 00000000000..fb8409b33c0 --- /dev/null +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -0,0 +1,153 @@ +############################ +### Fast FMatrix methods ### +############################ +from itertools import product +from os import getpid +from sage.combinat.root_system.poly_tup_engine cimport * +from sage.rings.ideal import Ideal +from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +cimport cython + +#Define a global temporary worker results repository +worker_results = list() + +############### +### Mappers ### +############### + +#Given an FMatrix factory and a sextuple, return a pentagon equation as a polynomial object +cdef req_cy(factory, tuple sextuple, side="left"): + a, b, c, d, e, g = sextuple + #To add typing we need to ensure all fmats.fmat are of the same type? + #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? + lhs = factory.FR.r_matrix(a,c,e)*factory.fmat(a,c,b,d,e,g)*factory.FR.r_matrix(b,c,g) + rhs = 0 + for f in factory.FR.basis(): + rhs += factory.fmat(c,a,b,d,e,f)*factory.FR.r_matrix(f,c,d)*factory.fmat(a,b,c,d,f,g) + return lhs-rhs + +#Set up and reduce the hexagon equations corresponding to this worker +@cython.wraparound(False) +@cython.nonecheck(False) +@cython.cdivision(True) +cpdef get_reduced_hexagons(factory, int child_id, int n_proc): + global worker_results + cdef int i + cdef tuple sextuple + for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): + if i % n_proc == child_id: + he = req_cy(factory,sextuple) + if he: + worker_results.append(reduce_poly_dict(he.dict(),factory._nnz,factory._ks)) + +#Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object +cdef MPolynomial_libsingular feq_cy(factory, tuple nonuple, bint prune=False): + a, b, c, d, e, f, g, k, l = nonuple + cdef lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) + if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information + return factory._poly_ring.zero() + cdef rhs = factory._poly_ring.zero() + for h in factory.FR.basis(): + rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) + return lhs - rhs + +#Set up and reduce the pentagon equations corresponding to this worker +@cython.wraparound(False) +@cython.nonecheck(False) +@cython.cdivision(True) +cpdef get_reduced_pentagons(factory, int child_id, int n_proc, bint prune=True): + cdef int i + cdef tuple nonuple + cdef MPolynomial_libsingular pe + for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): + if i % n_proc == child_id: + pe = feq_cy(factory,nonuple,prune=prune) + if pe: + worker_results.append(reduce_poly_dict(pe.dict(),factory._nnz,factory._ks)) + +#Substitute known values, known squares, and reduce! +cpdef update_reduce(factory, tuple eq_tup): + global worker_results + cdef dict eq_dict = subs(eq_tup,factory._kp) + cdef reduced + if tup_fixes_sq(tuple(eq_dict.items())): + reduced = to_monic(eq_dict) + else: + reduced = reduce_poly_dict(eq_dict,factory._nnz,factory._ks) + worker_results.append(reduced) + +#Compute Groebner basis for given equations iterable +cpdef compute_gb(factory,eqns,term_order="degrevlex"): + global worker_results + #Define smaller poly ring in component vars + sorted_vars = list() + for eq_tup in eqns: + for fx in variables(eq_tup): + sorted_vars.append(fx) + sorted_vars = sorted(set(sorted_vars)) + R = PolynomialRing(factory.FR.field(),len(sorted_vars),'a',order=term_order) + + #Zip tuples into R and compute Groebner basis + cdef idx_map = dict() + for new, old in enumerate(sorted_vars): + idx_map[old] = new + nvars = len(sorted_vars) + cdef list polys = list() + for eq_tup in eqns: + polys.append(tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R)) + gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") + + #Change back to fmats poly ring and append to temp_eqns + cdef dict inv_idx_map = dict() + for k, v in idx_map.items(): + inv_idx_map[v] = k + nvars = factory._poly_ring.ngens() + for p in gb: + worker_results.append(resize(poly_to_tup(p),inv_idx_map,nvars)) + +################ +### Reducers ### +################ + +#Helper function for returning processed results back to parent process +#Trivial reducer: simply collects objects with the same key in the worker +def collect_eqns(proc): + #Discard the zero polynomial + global worker_results + reduced = set(worker_results)-set([tuple()]) + worker_results = list() + return list(reduced) + +#################### +### Verification ### +#################### + +#Check the pentagon equation corresponding to the given nonuple +cdef feq_verif(factory, tuple nonuple, float tol=5e-8): + global worker_results + a, b, c, d, e, f, g, k, l = nonuple + cdef float diff, lhs, rhs + lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) + rhs = 0.0 + for h in factory.FR.basis(): + rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) + diff = lhs - rhs + if diff > tol or diff < -tol: + worker_results.append(diff) + +#Generate all the pentagon equations assigned to this process, and reduce them +@cython.wraparound(False) +@cython.nonecheck(False) +@cython.cdivision(True) +cpdef pent_verify(tuple mp_params, factory): + child_id, n_proc = mp_params + cdef float t0 + cdef tuple nonuple + cdef long i + for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): + if i % n_proc == child_id: + feq_verif(factory,nonuple) + if i % 50000000 == 0 and i: + print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) + print("Ran through {} pentagons... Worker {} with pid {} reporting {} potential misses...".format(i,child_id,getpid(),len(worker_results))) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 667b71760d4..811935103b0 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -3,8 +3,9 @@ """ # **************************************************************************** # Copyright (C) 2019 Daniel Bump -# Nicolas Thiery # Guillermo Aboumrad +# Travis Scrimshaw +# Nicolas Thiery # # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ @@ -19,6 +20,11 @@ from sage.rings.number_field.number_field import CyclotomicField from sage.misc.cachefunc import cached_method +from itertools import product +import sage.combinat.root_system.f_matrix as FMatrix +from sage.misc.lazy_attribute import lazy_attribute +from sage.matrix.special import diagonal_matrix + class FusionRing(WeylCharacterRing): r""" Return the Fusion Ring (Verlinde Algebra) of level ``k``. @@ -116,7 +122,7 @@ class FusionRing(WeylCharacterRing): The fusion ring has a number of methods that reflect its role as the Grothendieck ring of a *modular tensor category* (MTC). These include twist methods :meth:`Element.twist` and :meth:`Element.ribbon` - for its elements related to the ribbon structure, and the + for its elements related to the ribbon structure, and the S-matrix :meth:`s_ij`. There are two natural normalizations of the S-matrix. Both @@ -128,7 +134,7 @@ class FusionRing(WeylCharacterRing): The unitary S-matrix is `s=D^{-1/2}\tilde{s}` where .. MATH:: - + D = \sum_V d_i(V)^2. The sum is over all simple objects `V` with @@ -546,7 +552,7 @@ def virasoro_central_charge(self): field theory associated with the Fusion Ring. If `\mathfrak{g}` is the corresponding semisimple Lie algebra, this is - + .. MATH:: \frac{k\dim\mathfrak{g}}{k+h^\vee}, @@ -561,7 +567,7 @@ def virasoro_central_charge(self): is computed in :meth:`D_plus` and `D = \sum d_i^2 > 0` is computed by :meth:`global_q_dimension`. Squaring this identity and remembering that `D_+ D_- = D` gives - + .. MATH:: D_+ / D_- = e^{i\pi c/2}. @@ -706,7 +712,7 @@ def s_matrix(self, unitary=False): - ``unitary`` -- (default: ``False``) set to ``True`` to obtain the unitary S-matrix - Without the ``unitary`` parameter, this is the matrix denoted + Without the ``unitary`` parameter, this is the matrix denoted `\widetilde{s}` in [BaKi2001]_. EXAMPLES:: @@ -734,12 +740,19 @@ def s_matrix(self, unitary=False): return S / self.total_q_order() else: return S - + + @cached_method def r_matrix(self, i, j, k): r""" Return the R-matrix entry corresponding to the subobject ``k`` in the tensor product of ``i`` with ``j``. + .. WARNING:: + + This method only gives complete information when `N_{ij}^k = 1` + (an important special case). Tables of MTC including R-matrices + may be found in Section 5.3 of [RoStWa2009]_ and in [Bond2007]_. + The R-matrix is a homomorphism `i \otimes j \rightarrow j \otimes i`. This may be hard to describe since the object `i \otimes j` may be reducible. However if `k` is a simple subobject of @@ -749,21 +762,15 @@ def r_matrix(self, i, j, k): R-matrix. This method computes that scalar. It is possible to adjust the set of embeddings `k \rightarrow i \otimes j` (called a *gauge*) so that this scalar equals - + .. MATH:: \pm \sqrt{\frac{ \theta_k }{ \theta_i \theta_j }}. If `i \neq j`, the gauge may be used to control the sign of the square root. But if `i = j` then we must be careful - about the sign. This sign is `+` if `k` is a subobject of - the symmetric square of `i` and `-` if it is a subobject of - the exterior square. See [LR1997]_ Corollary 2.22 - (actually due to Reshetikhin). - - This method only gives complete information when `N_{ij}^k = 1` - (an important special case). Tables of MTC including R-matrices - may be found in Section 5.3 of [RoStWa2009]_ and in [Bond2007]_. + about the sign. These cases are computed by a formula + of [BDGRTW2019]_, Proposition 2.3. EXAMPLES:: @@ -782,14 +789,14 @@ def r_matrix(self, i, j, k): """ if self.Nk_ij(i, j, k) == 0: return 0 - r = self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2) if i != j: - return r - wt = k.weight() - if wt in i.symmetric_power(2).monomial_coefficients(): - return r - # We instead have wt in i.exterior_power(2).monomial_coefficients(): - return -r + return self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2) + i0 = self.one() + B = self.basis() + return sum(y.ribbon()**2 / (i.ribbon() * x.ribbon()**2) + * self.s_ij(i0,y) * self.s_ij(i,z) * self.s_ij(x,z).conjugate() + * self.s_ij(k,x).conjugate() * self.s_ij(y,z).conjugate() / self.s_ij(i0,z) + for x in B for y in B for z in B) / (self.total_q_order()**4) def global_q_dimension(self): r""" @@ -864,6 +871,176 @@ def D_minus(self): """ return sum((x.q_dimension())**2 / x.ribbon() for x in self.basis()) + ################################### + ### Braid group representations ### + ################################### + + #Recursively enumerate all the admissible trees with given top row and root. + #Returns a list of tuples (l1,...,lk) such that + #root -> lk # m[-1], lk -> l_{k-1} # m[-2], ..., l1 -> m[0] # m[1], + #with top_row = m + def get_trees(self,top_row,root): + if len(top_row) == 2: + m1, m2 = top_row + return [[]] if self.Nk_ij(m1,m2,root) else [] + else: + m1, m2 = top_row[:2] + return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] + + #Get the so-called computational basis for Hom(b, a^n). The basis is a list of + #(n-2)-tuples (m_1,...,m_{n//2},l_1,...,l_{(n-3)//2}) such that + #each m_i is a monomial in a^2 and l_{j+1} \in l_j # a, and l[-1] \in a # b + def get_comp_basis(self,a,b,n_strands): + comp_basis = list() + for top in product((a*a).monomials(),repeat=n_strands//2): + #If the n_strands is odd, we must extend the top row by a fusing anyon + top_row = list(top)+[a]*(n_strands%2) + comp_basis.extend(tuple([*top,*levels]) for levels in self.get_trees(top_row,b)) + return comp_basis + + #Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two strands + #in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j + #of trees b -> xj # yj -> (a # a) # (a # a) + @cached_method + def mid_sig_ij(self,row,col,a,b): + xi, yi = row + xj, yj = col + entry = 0 + for c in self.basis(): + for d in self.basis(): + ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) + f1 = self.fmats.fmat(a,a,yi,b,xi,c) + f2 = self.fmats.fmat(a,a,a,c,d,yi) + f3 = self.fmats.fmat(a,a,a,c,d,yj) + f4 = self.fmats.fmat(a,a,yj,b,xj,c) + r = self.r_matrix(a,a,d) + entry += f1 * f2 * r * f3 * f4 + return entry + + #Compute the xi, xj entry of the braid generator on the right-most strands, + #corresponding to the tree b -> (xi # a) -> (a # a) # a, which results in a + #sum over j of trees b -> xj -> (a # a) # (a # a) + @cached_method + def odd_one_out_ij(self,xi,xj,a,b): + entry = 0 + for c in self.basis(): + ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) + f1 = self.fmats.fmat(a,a,a,b,xi,c) + f2 = self.fmats.fmat(a,a,a,b,xj,c) + r = self.r_matrix(a,a,c) + entry += f1 * r * f2 + return entry + + @lazy_attribute + def fmats(self): + return FMatrix.FMatrix(self) + + #Compute generators of the Artin braid group on n_strands strands. If + #fusing_anyon = a and total_topological_charge = b, the generators are + #endomorphisms of Hom(a^n_strands, b). + ###NOTE: For now, we assume existence of fmats with relevant F-symbols ready. + #In the future this method will call an appropriate F-matrix solver... + #For useful group calculations, the F-symbols should lie in a NumberField. + def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands): + assert n_strands > 2, "The number of strands must be an integer greater than 2" + a, b = fusing_anyon, total_topological_charge + + #Construct associated FMatrix object and solve for F-symbols + if not self.fmats.symbols_known: + self.fmats.find_real_orthogonal_solution() + + #Set up the computational basis + comp_basis = self.get_comp_basis(a,b,n_strands) + basis_dict = { elt : i for i, elt in enumerate(comp_basis) } + dim = len(comp_basis) + print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(dim,n_strands)) + + #Compute diagonal odd-indexed generators using the 3j-symbols + gens = { 2*i+1 : diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2) } + + #Compute even-indexed generators using F-matrices + for k in range(1,n_strands//2): + entries = dict() + for i in range(dim): + for f,e,q in product(self.basis(),repeat=3): + #Compute appropriate possible nonzero row index + nnz_pos = list(comp_basis[i]) + nnz_pos[k-1:k+1] = f,e + #Handle the special case k = 1 + if k > 1: + nnz_pos[n_strands//2+k-2] = q + nnz_pos = tuple(nnz_pos) + + #Skip repeated entries when k = 1 + if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in entries: + m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] + #A few special cases + top_left = m[0] + if k >= 3: + top_left = l[k-3] + root = b + if k - 1 < len(l): + root = l[k-1] + + #Handle the special case k = 1 + if k == 1: + entries[basis_dict[nnz_pos],i] = self.mid_sig_ij(m[:2],(f,e),a,root) + continue + + entry = 0 + for p in self.basis(): + f1 = self.fmats.fmat(top_left,m[k-1],m[k],root,l[k-2],p) + f2 = self.fmats.fmat(top_left,f,e,root,q,p) + entry += f1 * self.mid_sig_ij((m[k-1],m[k]),(f,e),a,p) * f2 + entries[basis_dict[nnz_pos],i] = entry + gens[2*k] = matrix(entries) + + #If n_strands is odd, we compute the final generator + if n_strands % 2: + entries = dict() + for i in range(dim): + for f, q in product(self.basis(),repeat=2): + #Compute appropriate possible nonzero row index + nnz_pos = list(comp_basis[i]) + nnz_pos[n_strands//2-1] = f + #Handle small special case + if n_strands > 3: + nnz_pos[-1] = q + nnz_pos = tuple(nnz_pos) + + if nnz_pos in comp_basis: + m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] + + #Handle a couple of small special cases + if n_strands == 3: + entries[basis_dict[nnz_pos],i] = self.odd_one_out_ij(m[-1],f,a,b) + continue + top_left = m[0] + if n_strands > 5: + top_left = l[-2] + root = b + + #Compute relevant entry + entry = 0 + for p in self.basis(): + f1 = self.fmats.fmat(top_left,m[-1],a,root,l[-1],p) + f2 = self.fmats.fmat(top_left,f,a,root,q,p) + entry += f1 * self.odd_one_out_ij(m[-1],f,a,p) * f2 + entries[basis_dict[nnz_pos],i] = entry + gens[n_strands-1] = matrix(entries) + + return comp_basis, [gens[k] for k in sorted(gens)] + + #A useful sanity check + #Determine if given iterable of n matrices defines a representation of + #the Artin braid group on (n+1) strands + def gens_satisfy_braid_gp_rels(self,sig): + n = len(sig) + braid_rels = all(sig[i] * sig[i+1] * sig[i] == sig[i+1] * sig[i] * sig[i+1] for i in range(n-1)) + far_comm = all(sig[i] * sig[j] == sig[j] * sig[i] for i, j in product(range(n),repeat=2) if abs(i-j) > 1 and i > j) + singular = any(s.is_singular() for s in sig) + return braid_rels and far_comm and not singular + class Element(WeylCharacterRing.Element): """ A class for FusionRing elements. @@ -904,7 +1081,7 @@ def weight(self): def twist(self, reduced=True): r""" - Return a rational number `h` such that `\theta = e^{i \pi h}` + Return a rational number `h` such that `\theta = e^{i \pi h}` is the twist of ``self``. The quantity `e^{i \pi h}` is also available using :meth:`ribbon`. @@ -913,7 +1090,7 @@ def twist(self, reduced=True): `h = \langle \lambda, \lambda+2\rho \rangle`, where `\rho` is half the sum of the positive roots. As in [Row2006]_, this requires normalizing - the invariant bilinear form so that + the invariant bilinear form so that `\langle \alpha, \alpha \rangle = 2` for short roots. INPUT: @@ -1026,4 +1203,3 @@ def q_dimension(self): expr = expr.substitute(q=q**4) / (q**(2*expr.degree())) zet = P.field().gen() ** (P._cyclotomic_order/P._l) return expr.substitute(q=zet) - diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 28f5d9138bc..78d4dffa4e5 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -1,5 +1,9 @@ from sage.rings.polynomial.polydict cimport ETuple +from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular + +cpdef tuple poly_to_tup(MPolynomial_libsingular poly) +cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) cdef ETuple degrees(tuple poly_tup) cpdef ETuple get_variables_degrees(list eqns) @@ -14,12 +18,4 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq) cpdef dict compute_known_powers(ETuple max_deg, dict val_dict) cpdef dict subs(tuple poly_tup, dict known_powers) cdef tuple tup_mul(tuple p1, tuple p2) -cpdef update_reduce(tuple eq_tup, factory, mr_eng) cpdef int poly_tup_cmp(tuple tleft, tuple tright) -cpdef tuple req_cy(factory, tuple sextuple, side=*) -cpdef tuple feq_cy(factory, tuple nonuple, bint prune=*) -cpdef feq_verif(factory, tuple nonuple, float tol=*) -cpdef pent_verify(tuple mp_params, factory) -cpdef tuple rationalify(factory, tuple eq_tup) -cpdef tuple cyclotomify(factory, tuple rat_tup) - diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 02c7a6f7c2e..beacc925e4d 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -1,12 +1,9 @@ +from functools import cmp_to_key from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular -from sage.rings.polynomial.polydict cimport ETuple, PolyDict +from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.term_order import TermOrder from sage.rings.rational_field import RationalField as QQ -###NOTE: issues with importing NumberFieldElement_absolute and FMatrix -#from sage.rings.number_field.number_field_element cimport NumberFieldElement -#from sage.combinat.root_system.f_matrix import FMatrix - ################################################### ### Arithmetic Engine for polynomials as tuples ### ################################################### @@ -36,7 +33,7 @@ cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingu #Given a tuple of pairs representing a univariate polynomial and a univariate #polynomial ring generator, return a univariate polynomial object -def tup_to_univ_poly(eq_tup, gen): +def tup_to_univ_poly(tuple eq_tup, gen): univ_tup = tuple((exp.nonzero_values()[0] if exp.nonzero_values() else 0, c) for exp, c in eq_tup) return sum(c * gen ** p for p, c in univ_tup) @@ -210,20 +207,12 @@ cdef tuple tup_mul(tuple p1, tuple p2): prod[xi.eadd(yj)] = ai*bj return tuple(prod.items()) -#Substitute known values, known squares, and reduce! -cpdef update_reduce(tuple eq_tup, factory, mr_eng): - cdef dict eq_dict = subs(eq_tup,factory._kp) - cdef reduced - if tup_fixes_sq(tuple(eq_dict.items())): - reduced = to_monic(eq_dict) - else: - reduced = reduce_poly_dict(eq_dict,factory._nnz,factory._ks) - mr_eng.worker_results.append(reduced) - ############### ### Sorting ### ############### +#Implement richcmp comparator object that can be passed in as key to sorted method + #Determine which polynomial is larger with respect to the degrevlex ordering cpdef int poly_tup_cmp(tuple tleft, tuple tright): cdef int i, ret, sf, sg, val @@ -244,147 +233,3 @@ cpdef int poly_tup_cmp(tuple tleft, tuple tright): if ret != 0: return ret return len(tleft) - len(tright) - -############################ -### Fast FMatrix methods ### -############################ -from itertools import product - -#Given an FMatrix factory and a sextuple, return a pentagon equation as a polynomial object -cpdef tuple req_cy(factory, tuple sextuple, side="left"): - a, b, c, d, e, g = sextuple - #To add typing we need to ensure all fmats.fmat are of the same type? - #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = factory.FR.r_matrix(a,c,e)*factory.fmat(a,c,b,d,e,g)*factory.FR.r_matrix(b,c,g) - rhs = 0 - for f in factory.FR.basis(): - rhs += factory.fmat(c,a,b,d,e,f)*factory.FR.r_matrix(f,c,d)*factory.fmat(a,b,c,d,f,g) - he = lhs - rhs - if he: - return reduce_poly_dict(he.dict(),factory._nnz,factory._ks) - else: - return tuple() - -#Set up and reduce the hexagon equations corresponding to this worker -# cpdef get_reduced_hexagons(factory, int child_id, int n_proc): -# cdef int i -# cdef tuple sextuple -# for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): -# if i % n_proc == child_id: -# he = req_cy(factory,sextuple) -# if he: -# factory.temp_eqns.append(reduce_poly_dict(he.dict(),factory._nnz,factory._ks)) - -#Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object - -cpdef tuple feq_cy(factory, tuple nonuple, bint prune=False): - a, b, c, d, e, f, g, k, l = nonuple - cdef lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) - if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information - return factory._poly_ring.zero() - cdef MPolynomial_libsingular rhs = factory._poly_ring.zero() - for h in factory.FR.basis(): - rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) - return reduce_poly_dict((lhs - rhs).dict(),factory._nnz,factory._ks) - -#Set up and reduce the pentagon equations corresponding to this worker -# cpdef get_reduced_pentagons(factory, int child_id, int n_proc, bint prune=True): -# cdef int i -# cdef tuple nonuple -# cdef MPolynomial_libsingular pe -# for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): -# if i % n_proc == child_id: -# pe = feq_cy(factory,nonuple,prune=prune) -# if pe: -# factory.temp_eqns.append(reduce_poly_dict(pe.dict(),factory._nnz,factory._ks)) - -#################### -### Verification ### -#################### - -#Check the pentagon equation corresponding to the given nonuple -cpdef feq_verif(factory, tuple nonuple, float tol=5e-8): - a, b, c, d, e, f, g, k, l = nonuple - cdef float diff, lhs, rhs - lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) - rhs = 0.0 - for h in factory.FR.basis(): - rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) - diff = lhs - rhs - if diff > tol or diff < -tol: - factory.temp_eqns.append(diff) - -#Generate all the pentagon equations assigned to this process, and reduce them -from time import time -from os import getpid -cimport cython -@cython.wraparound(False) -@cython.nonecheck(False) -@cython.cdivision(True) -cpdef pent_verify(tuple mp_params, factory): - child_id, n_proc = mp_params - cdef float t0 - cdef tuple nonuple - cdef long i - t0 = time() - for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): - if i % n_proc == child_id: - feq_verif(factory,nonuple) - if i % 50000000 == 0 and i: - print("{:5d}m equations checked in {:8.2f}... {} misses so far...".format(i // 1000000,time()-t0,len(factory.temp_eqns))) - print("Ran through {} pentagons in {:8.2f}... Worker {} with {} reporting {} total misses...".format(i,time()-t0,child_id,getpid(),len(factory.temp_eqns))) - -################################ -### Well, this was a bust... ### -################################ - -#Given a tuple representation of an n-variate polynomial over a cyclotomic field, -#return a tuple representation of an (n+1)-variate polynomial over the rationals, -#with the cyclotomic field generator treated as an indeterminate and used as the -#last variable in the parent polynomial ring -cpdef tuple rationalify(factory, tuple eq_tup): - F = factory.FR.field() - cdef list rat_tup = list() - cdef ETuple exp - for exp, coeff in eq_tup: - #In the future we should avoid casting by ensuring all coeffs created are - #in fmats.FR.field (eg. cython reducers, FMatrix.fmat) - for cyc_power, rat_coeff in F(coeff).polynomial().dict().items(): - nnz = { len(exp) : cyc_power } - for idx, val in exp.sparse_iter(): - nnz[idx] = val - new_e = ETuple(nnz, len(exp)+1) - rat_tup.append((new_e, rat_coeff)) - return tuple(rat_tup) - -#Given a tuple representation of an n-variate polynomial over the rationals, -#return a tuple repn of the polynomial with integer coefficients, obtained by -#multiplying all coefficients by the least common multiple of denominators -# cpdef tuple integralify(tuple rat_tup): -# cdef int cdenom = LCM(coeff.denominator() for exp, coeff in rat_tup) -# cdef list int_tup = list() -# for exp, coeff in rat_tup: -# int_tup.append((exp, int(cdenom * coeff))) -# return tuple(int_tup) - -#Left inverse of rationalify... -#cylcotomify(factory, rationalify(factory, eq_tup)) == eq_tup -cpdef tuple cyclotomify(factory, tuple rat_tup): - gen = factory.FR.field().gen() - cdef cyc_dict = dict() - cdef ETuple exp - for exp, coeff in rat_tup: - cyc_pow = exp.get_exp(len(exp)-1) - new_e = ETuple({ pos : val for pos, val in exp.sparse_iter() if pos != len(exp)-1 }, len(exp)-1) - if new_e in cyc_dict: - cyc_dict[new_e] += gen ** cyc_pow * coeff - else: - cyc_dict[new_e] = gen ** cyc_pow * coeff - return tuple(cyc_dict.items()) - -# def clear_denom(eq): -# for var in eq.variables(): -# if int(str(var)[1:]) >= fmats._poly_ring.ngens(): -# d = eq.degree(var) -# eq *= var ** d -# return eq From c229fb1e07ee75f296e7305987f7b258dab7e497 Mon Sep 17 00:00:00 2001 From: dwbmscz <75940445+dwbmscz@users.noreply.github.com> Date: Wed, 24 Feb 2021 13:40:18 -0800 Subject: [PATCH 004/414] remove unneeded map_reduce_engine --- .../root_system/map_reduce_engine.pxd | 7 -- .../root_system/map_reduce_engine.pyx | 118 ------------------ 2 files changed, 125 deletions(-) delete mode 100644 src/sage/combinat/root_system/map_reduce_engine.pxd delete mode 100644 src/sage/combinat/root_system/map_reduce_engine.pyx diff --git a/src/sage/combinat/root_system/map_reduce_engine.pxd b/src/sage/combinat/root_system/map_reduce_engine.pxd deleted file mode 100644 index 15b5aeb0607..00000000000 --- a/src/sage/combinat/root_system/map_reduce_engine.pxd +++ /dev/null @@ -1,7 +0,0 @@ -cdef class MapReduceEngine(): - cdef public list worker_results - cdef public object input_iter - cdef public int mp_thresh - - cpdef map_caller(self,mp_params,mapper,extra_args=*) - diff --git a/src/sage/combinat/root_system/map_reduce_engine.pyx b/src/sage/combinat/root_system/map_reduce_engine.pyx deleted file mode 100644 index 3bd01615aac..00000000000 --- a/src/sage/combinat/root_system/map_reduce_engine.pyx +++ /dev/null @@ -1,118 +0,0 @@ -############################ -### Parlallel processing ### -############################ -from functools import partial -cimport cython - -#Rudimentary framework for performing MapReduce style computations -#DISCLAIMER: does not -cdef class MapReduceEngine(): - def __init__(self, mp_thresh=2500): - self.worker_results = list() - self.input_iter = None - #Multiprocess size parameter - self.mp_thresh = mp_thresh - - ################ - ### Reducers ### - ################ - - #Helper function for returning processed results back to parent process - #Trivial reducer: simply collects objects with the same key in the worker - def reduce_single_proc(self,proc): - #Discard the zero polynomial - reduced = set(self.worker_results)-set([tuple()]) - self.worker_results = list() - return list(reduced) - - #All-to-one communication step - def reduce_multi_process(self,reducer,worker_pool): - collected_eqns = set() - for child_eqns in worker_pool.imap_unordered(reducer,range(worker_pool._processes)): - collected_eqns.update(child_eqns) - return list(collected_eqns) - - ################# - ### MapReduce ### - ################# - - #Map fn across input_iter and reduce output to polynomial - #If worker_pool is not provided, function maps and reduces on a single process. - #If worker_pool is provided, the function attempts to determine whether it should - #use multiprocessing based on the length of the input iterable. If it can't determine - #the length of the input iterable then it uses multiprocessing with the default chunksize of 1 - #if chunksize is not explicitly provided. - def map_triv_reduce(self,mapper,input_iter,reducer=None,worker_pool=None,chunksize=None,mp_thresh=None): - #Compute multiprocessing parameters - if worker_pool is not None: - try: - n = len(input_iter) - except: - n = mp_thresh + 1 - if chunksize is None: - chunksize = n // (worker_pool._processes**2) + 1 - if mp_thresh is None: - mp_thresh = self.mp_thresh - no_mp = worker_pool is None or n < mp_thresh - #Map phase. Casting Async Object blocks execution... Each process holds results - #in its copy of fmats.temp_eqns - if no_mp: - list(map(mapper,input_iter)) - else: - list(worker_pool.imap_unordered(mapper,input_iter,chunksize=chunksize)) - - #Early termination in case no reducer is provided - if reducer is None: - return - - #Reduce phase - if no_mp: - results = reducer(0) - else: - results = self.reduce_multi_process(reducer,worker_pool) - return results - - #Map the given mapper across the input iterable creted using - #input_iter_setter, and then reduce using the provided reducer. - #The input iterable is created in each worker process with minimal communication: - #the parent only tells woker how to create iterable, but does NOT send an - #iterator object or its contents - - ###INPUT: - #input_iter_setter is a function that takes as a single argument an integer - #0 <= proc <= worker_pool._processes. If worker_pool is None, then - #input_iter_setter(0) is called - #The function input_iter_setter should set mr_eng.input_iter to a desired value - #mapper is a function that calls mr_eng.map_caller with appropriate arguments, - #that are bound to the function inside each worker process - #mapper does NOT return; it appends results to mr_eng.worker_results - #reducer is a function that instructs the engine how to collect results - #corresponding to the same key in each worker process - #input_iter_setter, mapper, AND reducer are instructions that will be - #processed inside each worker in the provided pool - - ###NOTES: set_input_iter MUST be called BEFORE - def emap_no_comm(self,input_iter_setter,mapper,reducer,worker_pool=None): - n_proc = worker_pool._processes if worker_pool is not None else 1 - params = [(child_id, n_proc) for child_id in range(n_proc)] - self.map_triv_reduce(input_iter_setter,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) - return self.map_triv_reduce(mapper,params,reducer,worker_pool=worker_pool,chunksize=1,mp_thresh=0) - - @cython.wraparound(False) - @cython.nonecheck(False) - @cython.cdivision(True) - cpdef map_caller(self,mp_params,mapper,extra_args=None): - cdef int child_id, i, n_proc - child_id, n_proc = mp_params - #Plug in additional arguments, if any - if extra_args is not None: - mapper = partial(mapper,*extra_args) - if self.input_iter is None: - raise ValueError("set_input_iter must be used to create input iterable in each worker process") - for i, input in enumerate(self.input_iter): - if i % n_proc == child_id: - res = mapper(input) - if res: - self.worker_results.append(res) - #Re-set iterator - self.input_iter = None From 8767fe249873a2a7e36b3f3436e644121cdaace4 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Feb 2021 06:38:08 -0800 Subject: [PATCH 005/414] updated f_matrix to include executor code --- src/sage/combinat/root_system/f_matrix.py | 126 +++++++----------- .../fast_parallel_fmats_methods.pxd | 9 +- .../fast_parallel_fmats_methods.pyx | 83 ++++++++++-- 3 files changed, 123 insertions(+), 95 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 862cfc3514e..c93ca689779 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -35,6 +35,8 @@ from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics from sage.rings.real_double import RDF +from itertools import zip_longest + class FMatrix(): r"""Return an F-Matrix factory for a FusionRing. @@ -112,14 +114,12 @@ class FMatrix(): Due to the large number of equations we may fail to find a Groebner basis if there are too many variables. - EXAMPLES:: sage: I=FusionRing("E8",2,conjugate=True) sage: I.fusion_labels(["i0","p","s"],inject_variables=True) sage: f = FMatrix(I,inject_variables=True); f creating variables fx1..fx14 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients We've exported two sets of variables to the global namespace. @@ -168,10 +168,9 @@ class FMatrix(): EXAMPLES:: - sage: f.get_pentagons()[1:3] - [fx1*fx5 - fx7^2, fx5*fx8*fx13 - fx2*fx12] - - + sage: f.pentagon()[1:3] + equations: 41 + [-fx0*fx1 + fx1, -fx1*fx2^2 + fx1] sage: f.hexagon()[1:3] equations: 14 [fx1*fx5 + fx2, fx2 + 1] @@ -249,7 +248,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.symbols_known = False #Multiprocessing attributes - self.mp_thresh = 7500 + self.mp_thresh = 10000 ####################### @@ -562,7 +561,13 @@ def load_fvars(self,save_dir=""): ### MapReduce ### ################# - #Map fn across input_iter and reduce output to polynomial + #Apply the given mapper to each element of the given input iterable and + #return the results (with no duplicates) in a list. This method applies the + #mapper in parallel if a worker_pool is provided. + ## INPUT: + #mapper is a string specifying the name of a function defined in the + #fast_parallel_fmats_methods module. + ###NOTES: #If worker_pool is not provided, function maps and reduces on a single process. #If worker_pool is provided, the function attempts to determine whether it should #use multiprocessing based on the length of the input iterable. If it can't determine @@ -582,10 +587,11 @@ def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_th no_mp = worker_pool is None or n < mp_thresh #Map phase. Casting Async Object blocks execution... Each process holds results #in its copy of fmats.temp_eqns + input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) if no_mp: - list(map(mapper,input_iter)) + list(map(executor,input_iter)) else: - list(worker_pool.imap_unordered(mapper,input_iter,chunksize=chunksize)) + list(worker_pool.imap_unordered(executor,input_iter,chunksize=chunksize)) #Reduce phase if no_mp: results = collect_eqns(0) @@ -593,41 +599,6 @@ def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_th results = self.reduce_multi_process(collect_eqns,worker_pool) return results - ###################### - ### Mapper callers ### - ###################### - - #Map callers are called in the worker processes. Map callers are used to supply - #the worker's local copy of forked variables to the relevant function - - #Substitute known values, known squares, and reduce a given equation - def update_reduce_caller(self,eq_tup): - update_reduce(self,eq_tup) - - #Compute Groebner basis for ideal defined by the given equations - def compute_gb_caller(self,eqns,term_order="degrevlex"): - compute_gb(self,eqns,term_order) - - #Construct reduced hexagons in a worker using the worker's copy of fmats - def reduced_hex_caller(self,mp_params): - get_reduced_hexagons(self,*mp_params) - - #Construct reduced pentagons in a worker using the worker's copy of fmats - def reduced_pent_caller(self,mp_params,prune=True): - get_reduced_pentagons(self,*mp_params) - - #One-to-all communication used to update fvars after triangular elim step. - def update_child_fmats(self,data_tup): - #fmats is assumed to be global before forking used to create the Pool object, - #so each child has a global fmats variable. So it's enough to update that object - self._fvars, self.solved, self._ks, self._var_degs = data_tup - self._nnz = self.get_known_nonz() - self._kp = compute_known_powers(self._var_degs,self.get_known_vals()) - - #Verify satisfaction of pentagon equations - def pent_verify_caller(self,mp_params): - pent_verify(mp_params,self) - ################ ### Reducers ### ################ @@ -643,6 +614,10 @@ def reduce_multi_process(self,reducer,worker_pool): ### Equations set up ### ######################## + #Get constraints making F-matrices orthogonal + #If output=True, equations are returned as polynomial objects. Otherwise, + #polynomial generators (stored in the internal tuple representation) are + #appended to self.ideal_basis def get_orthogonality_constraints(self,output=True): eqns = list() for tup in product(self.FR.basis(), repeat=4): @@ -652,23 +627,20 @@ def get_orthogonality_constraints(self,output=True): return eqns self.ideal_basis.extend([poly_to_tup(eq) for eq in eqns]) + #Get the equations defining the ideal generated by the hexagon or + #pentagon relations. Use option='hexagons' or option='pentagons' + #to specify desired equations. + #If output=True, equations are returned as polynomial objects. Otherwise, + #polynomial generators (stored in the internal tuple representation) are + #appended to self.ideal_basis #If a worker_pool is passed, then we use multiprocessing - def get_hexagons(self,worker_pool=None,output=True): - n_proc = worker_pool._processes if worker_pool is not None else 1 - params = [(child_id, n_proc) for child_id in range(n_proc)] - he = self.map_triv_reduce(self.reduced_hex_caller,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) - if output: - return [self.tup_to_fpoly(h) for h in he] - self.ideal_basis.extend(he) - - #If a worker_pool is passed, then we use multiprocessing - def get_pentagons(self,worker_pool=None,output=True): + def get_defining_equations(self,option,worker_pool=None,output=True): n_proc = worker_pool._processes if worker_pool is not None else 1 params = [(child_id, n_proc) for child_id in range(n_proc)] - pe = self.map_triv_reduce(self.reduced_pent_caller,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + eqns = self.map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) if output: - return [self.tup_to_fpoly(h) for h in pe] - self.ideal_basis.extend(pe) + return [self.tup_to_fpoly(p) for p in eqns] + self.ideal_basis.extend(eqns) ############################ ### Equations processing ### @@ -727,7 +699,7 @@ def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes new_data = [(self._fvars,self.solved,self._ks,self._var_degs)]*n_proc - self.map_triv_reduce(self.update_child_fmats,new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + self.map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) #Perform triangular elimination of linear terms in two-term equations until no such terms exist #For optimal usage of TRIANGULAR elimination, pass in a SORTED list of equations @@ -755,7 +727,7 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T #Compute new reduction params, send to child processes if any, and update eqns self.update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) - eqns = sorted(self.map_triv_reduce(self.update_reduce_caller,eqns,worker_pool=worker_pool), key=poly_sortkey) + eqns = sorted(self.map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool), key=poly_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) @@ -802,26 +774,26 @@ def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose= small_comps = list() temp_eqns = list() - #For informative print statement - nmax = self.largest_fmat_size() - vars_by_size = list() - for i in range(nmax+1): - vars_by_size.append(self.get_fmats_by_size(i)) + # #For informative print statement + # nmax = self.largest_fmat_size() + # vars_by_size = list() + # for i in range(nmax+1): + # vars_by_size.append(self.get_fmats_by_size(i)) for comp, comp_eqns in self.partition_eqns(graph,verbose=verbose).items(): #Check if component is too large to process if len(comp) > 60: - fmat_size = 0 - #For informative print statement - for i in range(1,nmax+1): - if set(comp).issubset(vars_by_size[i]): - fmat_size = i - print("Component of size {} with vars in F-mats of size {} is too large to find GB".format(len(comp),fmat_size)) + # fmat_size = 0 + # #For informative print statement + # for i in range(1,nmax+1): + # if set(comp).issubset(vars_by_size[i]): + # fmat_size = i + # print("Component of size {} with vars in F-mats of size {} is too large to find GB".format(len(comp),fmat_size)) temp_eqns.extend(comp_eqns) else: small_comps.append(comp_eqns) - gb_calculator = partial(self.compute_gb_caller,term_order=term_order) - small_comp_gb = self.map_triv_reduce(gb_calculator,small_comps,worker_pool=worker_pool,chunksize=1,mp_thresh=50) + input_iter = zip_longest(small_comps,[],fillvalue=term_order) + small_comp_gb = self.map_triv_reduce('compute_gb',input_iter,worker_pool=worker_pool,chunksize=1,mp_thresh=50) ret = small_comp_gb + temp_eqns return ret @@ -913,7 +885,7 @@ def get_numeric_solution(self,eqns=None,verbose=True): #Solver #If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash #Supports "warm" start. Use load_fvars to re-start computation from checkpoint - def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): + def find_real_unitary_solution(self,use_mp=True,save_dir="",verbose=True): #Set multiprocessing parameters. Context can only be set once, so we try to set it try: set_start_method('fork') @@ -925,7 +897,7 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): #Set up hexagon equations and orthogonality constraints poly_sortkey = cmp_to_key(poly_tup_cmp) self.get_orthogonality_constraints(output=False) - self.get_hexagons(worker_pool=pool,output=False) + self.get_defining_equations('hexagons',worker_pool=pool,output=False) self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) @@ -945,7 +917,7 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): self.update_reduction_params(worker_pool=pool,children_need_update=True) #Set up pentagon equations in parallel, simplify, and eliminate variables - self.get_pentagons(worker_pool=pool,output=False) + self.get_defining_equations('pentagons',worker_pool=pool,output=False) if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) @@ -994,7 +966,7 @@ def verify_pentagons(self,use_mp=True,prune=False): pool = None n_proc = pool._processes if pool is not None else 1 params = [(child_id,n_proc) for child_id in range(n_proc)] - pe = self.map_triv_reduce(self.pent_verify_caller,params,worker_pool=pool,chunksize=1,mp_thresh=0) + pe = self.map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) if np.all(np.isclose(np.array(pe),0,atol=1e-7)): print("Success!!! Found valid F-symbols for {}".format(self.FR)) pe = None diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index bcebff6c376..63dde1f57cf 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -1,5 +1,6 @@ -cpdef get_reduced_hexagons(factory, int child_id, int n_proc) -cpdef get_reduced_pentagons(factory, int child_id, int n_proc, bint prune=*) +cpdef get_reduced_hexagons(factory, tuple mp_params) +cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=*) cpdef update_reduce(factory, tuple eq_tup) -cpdef compute_gb(factory,eqns,term_order=*) -cpdef pent_verify(tuple mp_params, factory) +cpdef compute_gb(factory, tuple args) +cpdef update_child_fmats(factory, tuple data_tup) +cpdef pent_verify(factory, tuple mp_params) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index fb8409b33c0..ad8ef9d5fee 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -1,40 +1,84 @@ ############################ ### Fast FMatrix methods ### ############################ +cimport cython +import ctypes from itertools import product from os import getpid +import sage from sage.combinat.root_system.poly_tup_engine cimport * from sage.rings.ideal import Ideal from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -cimport cython #Define a global temporary worker results repository worker_results = list() +############################## +### Parallel code executor ### +############################## + +#Execute a function defined in this module (sage.combinat.root_system.fast_parallel_fmats_methods) +#in a worker process, and supply the factory parameter by constructing a reference +#to the FMatrix object in the worker's memory adress space from its id +### NOTE: When the parent process is forked, each worker gets a copy of +#every global variable. The virtual memory address of object X in the parent +#process equals the VIRTUAL memory address of the copy of object X in each +#worker, so we may construct references to forked copies of X +def executor(params): + (fn_name, fmats_id), args = params + #Construct a reference to global FMatrix object in this worker's memory + fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value + mod = sage.combinat.root_system.fast_parallel_fmats_methods + #Bind module method to FMatrix object in worker process, and call the method + getattr(mod,fn_name)(fmats_obj,args) + ############### ### Mappers ### ############### +#Cython version of fmat class method. Using cdef for fastest dispatch +cdef _fmat(factory, a, b, c, d, x, y): + if factory.FR.Nk_ij(a,b,x) == 0 or factory.FR.Nk_ij(x,c,d) == 0 or factory.FR.Nk_ij(b,c,y) == 0 or factory.FR.Nk_ij(a,y,d) == 0: + return 0 + #Some known zero F-symbols + if a == factory.FR.one(): + if x == b and y == d: + return 1 + else: + return 0 + if b == factory.FR.one(): + if x == a and y == c: + return 1 + else: + return 0 + if c == factory.FR.one(): + if x == d and y == b: + return 1 + else: + return 0 + return factory._fvars[a,b,c,d,x,y] + #Given an FMatrix factory and a sextuple, return a pentagon equation as a polynomial object cdef req_cy(factory, tuple sextuple, side="left"): a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = factory.FR.r_matrix(a,c,e)*factory.fmat(a,c,b,d,e,g)*factory.FR.r_matrix(b,c,g) + lhs = factory.FR.r_matrix(a,c,e)*_fmat(factory,a,c,b,d,e,g)*factory.FR.r_matrix(b,c,g) rhs = 0 for f in factory.FR.basis(): - rhs += factory.fmat(c,a,b,d,e,f)*factory.FR.r_matrix(f,c,d)*factory.fmat(a,b,c,d,f,g) + rhs += _fmat(factory,c,a,b,d,e,f)*factory.FR.r_matrix(f,c,d)*_fmat(factory,a,b,c,d,f,g) return lhs-rhs #Set up and reduce the hexagon equations corresponding to this worker @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) -cpdef get_reduced_hexagons(factory, int child_id, int n_proc): - global worker_results - cdef int i +cpdef get_reduced_hexagons(factory, tuple mp_params): + cdef int child_id, n_proc, i + child_id, n_proc = mp_params cdef tuple sextuple + global worker_results for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): if i % n_proc == child_id: he = req_cy(factory,sextuple) @@ -44,22 +88,24 @@ cpdef get_reduced_hexagons(factory, int child_id, int n_proc): #Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object cdef MPolynomial_libsingular feq_cy(factory, tuple nonuple, bint prune=False): a, b, c, d, e, f, g, k, l = nonuple - cdef lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) + cdef lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information return factory._poly_ring.zero() cdef rhs = factory._poly_ring.zero() for h in factory.FR.basis(): - rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) + rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) return lhs - rhs #Set up and reduce the pentagon equations corresponding to this worker @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) -cpdef get_reduced_pentagons(factory, int child_id, int n_proc, bint prune=True): - cdef int i +cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): + cdef int child_id, n_proc, i + child_id, n_proc = mp_params cdef tuple nonuple cdef MPolynomial_libsingular pe + global worker_results for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): if i % n_proc == child_id: pe = feq_cy(factory,nonuple,prune=prune) @@ -78,7 +124,8 @@ cpdef update_reduce(factory, tuple eq_tup): worker_results.append(reduced) #Compute Groebner basis for given equations iterable -cpdef compute_gb(factory,eqns,term_order="degrevlex"): +cpdef compute_gb(factory, tuple args): + eqns, term_order = args global worker_results #Define smaller poly ring in component vars sorted_vars = list() @@ -106,6 +153,14 @@ cpdef compute_gb(factory,eqns,term_order="degrevlex"): for p in gb: worker_results.append(resize(poly_to_tup(p),inv_idx_map,nvars)) +#One-to-all communication used to update fvars after triangular elim step. +cpdef update_child_fmats(factory, tuple data_tup): + #fmats is assumed to be global before forking used to create the Pool object, + #so each child has a global fmats variable. So it's enough to update that object + factory._fvars, factory.solved, factory._ks, factory._var_degs = data_tup + factory._nnz = factory.get_known_nonz() + factory._kp = compute_known_powers(factory._var_degs,factory.get_known_vals()) + ################ ### Reducers ### ################ @@ -128,10 +183,10 @@ cdef feq_verif(factory, tuple nonuple, float tol=5e-8): global worker_results a, b, c, d, e, f, g, k, l = nonuple cdef float diff, lhs, rhs - lhs = factory.fmat(f,c,d,e,g,l)*factory.fmat(a,b,l,e,f,k) + lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) rhs = 0.0 for h in factory.FR.basis(): - rhs += factory.fmat(a,b,c,g,f,h)*factory.fmat(a,h,d,e,g,k)*factory.fmat(b,c,d,k,h,l) + rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) diff = lhs - rhs if diff > tol or diff < -tol: worker_results.append(diff) @@ -140,7 +195,7 @@ cdef feq_verif(factory, tuple nonuple, float tol=5e-8): @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) -cpdef pent_verify(tuple mp_params, factory): +cpdef pent_verify(factory, tuple mp_params): child_id, n_proc = mp_params cdef float t0 cdef tuple nonuple From d8fb553cd17e9793c93088affd700f51c4a8764b Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Feb 2021 06:48:07 -0800 Subject: [PATCH 006/414] work on docstrings --- src/sage/combinat/root_system/f_matrix.py | 58 +++++---- .../fast_parallel_fmats_methods.pyx | 99 +++++++++------ .../combinat/root_system/poly_tup_engine.pyx | 115 ++++++++++++------ 3 files changed, 179 insertions(+), 93 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index c93ca689779..b521b88c3d7 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -103,23 +103,25 @@ class FMatrix(): [Bond2007]_ worked out F-matrices may be found in [RoStWa2009]_ and [CuWa2015]_. - The F-matrix is only determined up to a gauge. This + The F-matrix is only determined up to a *gauge*. This is a family of embeddings `C\to A\otimes B` for simple objects `A,B,C` such that `\text{Hom}(C,A\otimes B)` is nonzero. Changing the gauge changes the F-matrix though not in a very essential way. By varying the gauge it is possible to make the F-matrices unitary, or it is possible - to make them cyclotomic. We choose the latter. + to make them cyclotomic. Due to the large number of equations we may fail to find a Groebner basis if there are too many variables. + EXAMPLES:: sage: I=FusionRing("E8",2,conjugate=True) sage: I.fusion_labels(["i0","p","s"],inject_variables=True) sage: f = FMatrix(I,inject_variables=True); f creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients We've exported two sets of variables to the global namespace. @@ -164,16 +166,17 @@ class FMatrix(): definition of a monoidal category. The hexagon relations reflect the axioms of a *braided monoidal category*, which are constraints on both the F-matrix and on - the R-matrix. + the R-matrix. Finally, orthogonality constraints + may be imposed to obtain a unitary F-matrix. EXAMPLES:: - sage: f.pentagon()[1:3] - equations: 41 - [-fx0*fx1 + fx1, -fx1*fx2^2 + fx1] - sage: f.hexagon()[1:3] - equations: 14 - [fx1*fx5 + fx2, fx2 + 1] + sage: f.get_pentagons()[1:3] + [fx1*fx5 - fx7^2, fx5*fx8*fx13 - fx2*fx12] + sage: f.get_hexagons()[1:3] + [fx10*fx12 + (-zeta128^32)*fx12*fx13 + (-zeta128^16)*fx12, fx0 - 1] + sage: f.get_orthogonality_constraints()[1:3] + [fx1^2 - 1, fx2^2 - 1] You may solve these 41+14=55 equations to compute the F-matrix. @@ -613,12 +616,15 @@ def reduce_multi_process(self,reducer,worker_pool): ######################## ### Equations set up ### ######################## - - #Get constraints making F-matrices orthogonal - #If output=True, equations are returned as polynomial objects. Otherwise, - #polynomial generators (stored in the internal tuple representation) are - #appended to self.ideal_basis + def get_orthogonality_constraints(self,output=True): + """ + Get equations imposed on the F-matrix by orthogonality. + + If output=True, equations are returned as polynomial objects. Otherwise, + polynomial generators (stored in the internal tuple representation) are + appended to self.ideal_basis + """ eqns = list() for tup in product(self.FR.basis(), repeat=4): mat = self.fmatrix(*tup) @@ -627,14 +633,22 @@ def get_orthogonality_constraints(self,output=True): return eqns self.ideal_basis.extend([poly_to_tup(eq) for eq in eqns]) - #Get the equations defining the ideal generated by the hexagon or - #pentagon relations. Use option='hexagons' or option='pentagons' - #to specify desired equations. - #If output=True, equations are returned as polynomial objects. Otherwise, - #polynomial generators (stored in the internal tuple representation) are - #appended to self.ideal_basis - #If a worker_pool is passed, then we use multiprocessing def get_defining_equations(self,option,worker_pool=None,output=True): + """ + Get the equations defining the ideal generated by the hexagon or + pentagon relations. + + Use option='hexagons' to get equations imposed on the F-matrix by the hexagon + relations in the definition of a braided category. + + Use option='pentagons' to get equations imposed on the F-matrix by the pentagon + relations in the definition of a monoidal category. + + If output=True, equations are returned as polynomial objects. Otherwise, + polynomial generators (stored in the internal tuple representation) are + appended to self.ideal_basis + If a worker_pool is passed, then we use multiprocessing + """ n_proc = worker_pool._processes if worker_pool is not None else 1 params = [(child_id, n_proc) for child_id in range(n_proc)] eqns = self.map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) @@ -885,7 +899,7 @@ def get_numeric_solution(self,eqns=None,verbose=True): #Solver #If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash #Supports "warm" start. Use load_fvars to re-start computation from checkpoint - def find_real_unitary_solution(self,use_mp=True,save_dir="",verbose=True): + def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): #Set multiprocessing parameters. Context can only be set once, so we try to set it try: set_start_method('fork') diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index ad8ef9d5fee..7add91bdcaf 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -1,6 +1,13 @@ -############################ -### Fast FMatrix methods ### -############################ +""" +Fast FMatrix methods +""" +# **************************************************************************** +# Copyright (C) 2021 Guillermo Aboumrad +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + cimport cython import ctypes from itertools import product @@ -59,8 +66,10 @@ cdef _fmat(factory, a, b, c, d, x, y): return 0 return factory._fvars[a,b,c,d,x,y] -#Given an FMatrix factory and a sextuple, return a pentagon equation as a polynomial object cdef req_cy(factory, tuple sextuple, side="left"): + """ + Given an FMatrix factory and a sextuple, return a hexagon equation as a polynomial object + """ a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? @@ -70,50 +79,59 @@ cdef req_cy(factory, tuple sextuple, side="left"): rhs += _fmat(factory,c,a,b,d,e,f)*factory.FR.r_matrix(f,c,d)*_fmat(factory,a,b,c,d,f,g) return lhs-rhs -#Set up and reduce the hexagon equations corresponding to this worker @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) cpdef get_reduced_hexagons(factory, tuple mp_params): - cdef int child_id, n_proc, i - child_id, n_proc = mp_params - cdef tuple sextuple - global worker_results - for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): - if i % n_proc == child_id: - he = req_cy(factory,sextuple) - if he: - worker_results.append(reduce_poly_dict(he.dict(),factory._nnz,factory._ks)) - -#Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object + """ + Set up and reduce the hexagon equations corresponding to this worker + """ + global worker_results + cdef int i, child_id, n_proc + child_id, n_proc = mp_params + cdef tuple sextuple + for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): + if i % n_proc == child_id: + he = req_cy(factory,sextuple) + if he: + worker_results.append(reduce_poly_dict(he.dict(),factory._nnz,factory._ks)) + cdef MPolynomial_libsingular feq_cy(factory, tuple nonuple, bint prune=False): + """ + Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object + """ a, b, c, d, e, f, g, k, l = nonuple cdef lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information - return factory._poly_ring.zero() + return factory._poly_ring.zero() cdef rhs = factory._poly_ring.zero() for h in factory.FR.basis(): rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) return lhs - rhs -#Set up and reduce the pentagon equations corresponding to this worker @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): - cdef int child_id, n_proc, i - child_id, n_proc = mp_params - cdef tuple nonuple - cdef MPolynomial_libsingular pe - global worker_results - for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): - if i % n_proc == child_id: - pe = feq_cy(factory,nonuple,prune=prune) - if pe: - worker_results.append(reduce_poly_dict(pe.dict(),factory._nnz,factory._ks)) - -#Substitute known values, known squares, and reduce! + """ + Set up and reduce the pentagon equations corresponding to this worker + """ + global worker_results + cdef int i, child_id, n_proc + child_id, n_proc = mp_params + cdef tuple nonuple + cdef MPolynomial_libsingular pe + for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): + if i % n_proc == child_id: + pe = feq_cy(factory,nonuple,prune=prune) + if pe: + worker_results.append(reduce_poly_dict(pe.dict(),factory._nnz,factory._ks)) + + cpdef update_reduce(factory, tuple eq_tup): + """ + Substitute known values, known squares, and reduce! + """ global worker_results cdef dict eq_dict = subs(eq_tup,factory._kp) cdef reduced @@ -123,10 +141,12 @@ cpdef update_reduce(factory, tuple eq_tup): reduced = reduce_poly_dict(eq_dict,factory._nnz,factory._ks) worker_results.append(reduced) -#Compute Groebner basis for given equations iterable cpdef compute_gb(factory, tuple args): - eqns, term_order = args + """ + Compute Groebner basis for given equations iterable + """ global worker_results + eqns, term_order = args #Define smaller poly ring in component vars sorted_vars = list() for eq_tup in eqns: @@ -165,9 +185,11 @@ cpdef update_child_fmats(factory, tuple data_tup): ### Reducers ### ################ -#Helper function for returning processed results back to parent process -#Trivial reducer: simply collects objects with the same key in the worker def collect_eqns(proc): + """ + Helper function for returning processed results back to parent process. + Trivial reducer: simply collects objects with the same key in the worker + """ #Discard the zero polynomial global worker_results reduced = set(worker_results)-set([tuple()]) @@ -178,8 +200,10 @@ def collect_eqns(proc): ### Verification ### #################### -#Check the pentagon equation corresponding to the given nonuple cdef feq_verif(factory, tuple nonuple, float tol=5e-8): + """ + Check the pentagon equation corresponding to the given nonuple + """ global worker_results a, b, c, d, e, f, g, k, l = nonuple cdef float diff, lhs, rhs @@ -191,11 +215,14 @@ cdef feq_verif(factory, tuple nonuple, float tol=5e-8): if diff > tol or diff < -tol: worker_results.append(diff) -#Generate all the pentagon equations assigned to this process, and reduce them + @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) cpdef pent_verify(factory, tuple mp_params): + """ + Generate all the pentagon equations assigned to this process, and reduce them + """ child_id, n_proc = mp_params cdef float t0 cdef tuple nonuple diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index beacc925e4d..f6edc15a235 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -1,26 +1,36 @@ +################################################### +### Arithmetic Engine for polynomials as tuples ### +################################################### +# **************************************************************************** +# Copyright (C) 2021 Guillermo Aboumrad +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + from functools import cmp_to_key from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.term_order import TermOrder from sage.rings.rational_field import RationalField as QQ -################################################### -### Arithmetic Engine for polynomials as tuples ### -################################################### - ########### ### API ### ########### -#Convert a polynomial object into the internal representation as tuple of -#(ETuple exp, NumberFieldElement coeff) pairs cpdef tuple poly_to_tup(MPolynomial_libsingular poly): + """ + Convert a polynomial object into the internal representation as tuple of + (ETuple exp, NumberFieldElement coeff) pairs + """ return tuple(poly.dict().items()) -#Return a polynomial object from its tuple representation. Inverse of poly_to_tup. -#poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup && tup_to_poly(poly_to_tup(eq), eq.parent()) == eq -#Assumes parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent): + """ + Return a polynomial object from its tuple representation. Inverse of poly_to_tup. + poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup && tup_to_poly(poly_to_tup(eq), eq.parent()) == eq + Assumes parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup + """ # Maybe the following is faster but we need to ensure all coefficients are # already in fmats._poly_ring.base_ring() so that implicit casting is avoided # (this is pretty slow) @@ -31,17 +41,21 @@ cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingu ### "Change rings" ### ###################### -#Given a tuple of pairs representing a univariate polynomial and a univariate -#polynomial ring generator, return a univariate polynomial object def tup_to_univ_poly(tuple eq_tup, gen): + """ + Given a tuple of pairs representing a univariate polynomial and a univariate + polynomial ring generator, return a univariate polynomial object + """ univ_tup = tuple((exp.nonzero_values()[0] if exp.nonzero_values() else 0, c) for exp, c in eq_tup) return sum(c * gen ** p for p, c in univ_tup) -#Return a tuple representing a polynomial in a ring with len(sorted_vars) generators -#This method is used for creating polynomial objects with the "right number" of -#variables for computing Groebner bases of the partitioned equations graph -#and for adding constraints ensuring certain F-symbols are nonzero cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars): + """ + Return a tuple representing a polynomial in a ring with len(sorted_vars) generators + This method is used for creating polynomial objects with the "right number" of + variables for computing Groebner bases of the partitioned equations graph + and for adding constraints ensuring certain F-symbols are nonzero + """ cdef ETuple new_e cdef list resized = list() for exp, c in eq_tup: @@ -53,8 +67,11 @@ cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars): ### Convenience methods ### ########################### -#Return the maximal degree of each variable in the polynomial + cdef ETuple degrees(tuple poly_tup): + """ + Return the maximal degree of each variable in the polynomial + """ #Deal with the empty tuple, representing the zero polynomial if not poly_tup: return ETuple() cdef ETuple max_degs, exp @@ -63,8 +80,10 @@ cdef ETuple degrees(tuple poly_tup): max_degs = max_degs.emax(exp) return max_degs -#Find maximum degrees for each variable in equations cpdef ETuple get_variables_degrees(list eqns): + """ + Find maximum degrees for each variable in equations + """ cdef tuple eq_tup cdef ETuple max_deg max_deg = degrees(eqns[0]) @@ -72,36 +91,47 @@ cpdef ETuple get_variables_degrees(list eqns): max_deg = max_deg.emax(degrees(eq_tup)) return max_deg -#Return indices of all variables appearing in eq_tup cpdef list variables(tuple eq_tup): + """ + Return indices of all variables appearing in eq_tup + """ return degrees(eq_tup).nonzero_positions() -#Return the constant coefficient of the polynomial represented by given tuple cpdef constant_coeff(tuple eq_tup): + """ + Return the constant coefficient of the polynomial represented by given tuple + """ for exp, coeff in eq_tup: if exp.is_constant(): return coeff return 0 cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): + """ + Apply `coeff_map` to coefficients + """ cdef list new_tup = list() for exp, coeff in eq_tup: new_tup.append((exp, coeff_map(coeff))) return tuple(new_tup) -#Determine if given equation fixes the square of a variable. An equation -#fixes the sq of a variable if it is of the form a*x^2 + c for NONZERO constants a, c cpdef bint tup_fixes_sq(tuple eq_tup): + """ + Determine if given equation fixes the square of a variable. An equation + fixes the sq of a variable if it is of the form `a*x^2 + c` for *nonzero* constants `a`, `c` + """ return len(eq_tup) == 2 and len(variables(eq_tup)) == 1 and eq_tup[0][0].nonzero_values() == [2] ###################### ### Simplification ### ###################### -#Substitutes for known squares in a given polynomial. -#The parameter known_sq is a dictionary of (int i, NumberFieldElement a) pairs such that x_i^2 - a == 0 -#Returns a dictionary of (ETuple, coeff) pairs representing polynomial cpdef dict subs_squares(dict eq_dict, dict known_sq): + """ + Substitutes for known squares in a given polynomial. + The parameter known_sq is a dictionary of (int i, NumberFieldElement a) pairs such that x_i^2 - a == 0 + Returns a dictionary of (ETuple, coeff) pairs representing polynomial + """ cdef dict subbed, new_e cdef ETuple exp, lm cdef int idx, power @@ -124,10 +154,12 @@ cpdef dict subs_squares(dict eq_dict, dict known_sq): subbed[exp] = coeff return { exp : a for exp, a in subbed.items() } -#Returns a dictionary of (ETuple, coeff) pairs describing the polynomial eq / GCF(eq) -#The input nonz is an ETuple indicating the positions of variables known to be nonzero. -#The entries of nonz are assumed to be some relatively large number, like 100 cdef dict remove_gcf(dict eq_dict, ETuple nonz): + """ + Returns a dictionary of (ETuple, coeff) pairs describing the polynomial eq / GCF(eq) + The input nonz is an ETuple indicating the positions of variables known to be nonzero. + The entries of nonz are assumed to be some relatively large number, like 100 + """ #Find common variables, filtered according to known nonzeros cdef ETuple common_powers, exp common_powers = nonz @@ -135,10 +167,12 @@ cdef dict remove_gcf(dict eq_dict, ETuple nonz): common_powers = common_powers.emin(exp) return { exp.esub(common_powers) : c for exp, c in eq_dict.items() } -#Return tuple of pairs (ETuple, coeff) describing the monic polynomial associated to eq_dict -#Here, the leading coefficient is chosen according to the degree reverse lexicographic ordering -#(default for multivariate polynomial rings) cdef tuple to_monic(dict eq_dict): + """ + Return tuple of pairs (ETuple, coeff) describing the monic polynomial associated to eq_dict + Here, the leading coefficient is chosen according to the degree reverse lexicographic ordering + (default for multivariate polynomial rings) + """ if not eq_dict: return tuple() it = reversed(sorted(eq_dict, key=TermOrder().sortkey_degrevlex)) lm = next(it) @@ -151,9 +185,11 @@ cdef tuple to_monic(dict eq_dict): ret.append((exp, inv_lc * eq_dict[exp])) return tuple(ret) -# Return a dictionary describing a monic polynomial with no known nonzero gcd and -# no known squares cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): + """ + Return a dictionary describing a monic polynomial with no known nonzero gcd and + no known squares + """ if not eq_dict: return tuple() return to_monic(remove_gcf(subs_squares(eq_dict, known_sq), nonz)) @@ -163,6 +199,9 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): #Pre-compute powers of known values for efficiency cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): + """ + Pre-compute powers of known values for efficiency + """ assert max(max_deg.nonzero_values(sort=False)) <= 100, "NotImplementedError: Cannot substitute for degree larger than 100" max_deg = max_deg.emin(ETuple({ idx : 100 for idx in val_dict }, len(max_deg))) cdef dict known_powers @@ -175,8 +214,10 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): known_powers[var_idx][power+1] = tup_mul(known_powers[var_idx][power],val_dict[var_idx]) return known_powers -#Substitute given variables into a polynomial tuple cpdef dict subs(tuple poly_tup, dict known_powers): + """ + Substitute given variables into a polynomial tuple + """ cdef dict subbed = {} cdef ETuple exp, m cdef int var_idx, power @@ -195,8 +236,10 @@ cpdef dict subs(tuple poly_tup, dict known_powers): subbed[exp.eadd(m)] = coeff*c return subbed -#Multiplication of two tuples... may have to make this faster cdef tuple tup_mul(tuple p1, tuple p2): + """ + Multiplication of two tuples... may have to make this faster + """ cdef dict prod = dict() cdef ETuple xi, yj for xi, ai in p1: @@ -213,8 +256,10 @@ cdef tuple tup_mul(tuple p1, tuple p2): #Implement richcmp comparator object that can be passed in as key to sorted method -#Determine which polynomial is larger with respect to the degrevlex ordering cpdef int poly_tup_cmp(tuple tleft, tuple tright): + """ + Determine which polynomial is larger with respect to the degrevlex ordering + """ cdef int i, ret, sf, sg, val cdef ETuple f, g ret = 0 From 1bf83afc8301bad32c0d97930c90816bf5407392 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:16:25 -0800 Subject: [PATCH 007/414] older cyclotomic method included --- src/sage/combinat/root_system/f_matrix.py | 152 +++++++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index b521b88c3d7..5bb41b12aea 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -171,9 +171,9 @@ class FMatrix(): EXAMPLES:: - sage: f.get_pentagons()[1:3] + sage: f.get_defininig_equations("pentagons")[1:3] [fx1*fx5 - fx7^2, fx5*fx8*fx13 - fx2*fx12] - sage: f.get_hexagons()[1:3] + sage: f.get_defining_equations("hexagons")[1:3] [fx10*fx12 + (-zeta128^32)*fx12*fx13 + (-zeta128^16)*fx12, fx0 - 1] sage: f.get_orthogonality_constraints()[1:3] [fx1^2 - 1, fx2^2 - 1] @@ -952,6 +952,154 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): self.save_fvars(filename) self.symbols_known = True + ######################### + ### Cyclotomic method ### + ######################### + + def fix_gauge(self, algorithm=''): + """ + Fix the gauge by forcing F-symbols not already fixed to equal 1. + This method should be used AFTER adding hex and pentagon eqns to ideal_basis + """ + while len(self.solved) < len(self._poly_ring.gens()): + #Get a variable that has not been fixed + #In ascending index order, for consistent results + for var in self._poly_ring.gens(): + if var not in self.solved: + break + + #Fix var = 1, substitute, and solve equations + self.ideal_basis.add(var-1) + print("adding equation...", var-1) + self.ideal_basis = set(Ideal(list(self.ideal_basis)).groebner_basis(algorithm=algorithm)) + self.substitute_degree_one() + self.update_equations() + + def substitute_degree_one(self, eqns=None): + if eqns is None: + eqns = self.ideal_basis + + new_knowns = set() + useless = set() + for eq in eqns: + #Substitute known value from univariate degree 1 polynomial or, + #Following Bonderson, p. 37, solve linear equation with two terms + #for one of the variables + if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self.solved: + self._fvars[self._var_to_sextuple[eq.lm()]] = -sum(c * m for c, m in zip(eq.coefficients()[1:], eq.monomials()[1:])) / eq.lc() + #Add variable to set of known values and remove this equation + new_knowns.add(eq.lm()) + useless.add(eq) + + #Solve equation of the form x_i x_j + k == 0 for x_i + # print("equation: ", eq, "variables ", eq.variables()) + # if eq.degree() == 2 and max(eq.degrees()) == 1 and len(eq.variables()) == 2 and eq.variable(0) not in self.solved: + # self._fvars[self._var_to_sextuple[str(eq.variable(0))]] = - eq.constant_coefficient() / eq.variable(1) + # print("Subbed {} for {}".format(- eq.constant_coefficient() / eq.variable(1), eq.variable(0))) + # #Add variable to set of known values and remove this equation + # new_knowns.add(eq.variable(0)) + # useless.add(eq) + + #Update fvars depending on other variables + self.solved.update(new_knowns) + for sextuple, rhs in self._fvars.items(): + d = { var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self.solved } + if len(d) == 2: print("THREE TERM LINEAR EQUATION ENCOUNTERED!") + if d: + self._fvars[sextuple] = rhs.subs(d) + + # if rhs.variables() and rhs.variable() in self.solved: + # assert rhs.is_univariate(), "RHS expression is not univariate" + # d = { rhs.variable() : } + # # print("Performing substitution of {} with dictionary {}".format(rhs, d)) + # self._fvars[sextuple] = rhs.subs(d) + + return new_knowns, useless + + def update_equations(self): + """ + Update ideal_basis equations by plugging in known values + """ + special_values = { known : self._fvars[self._var_to_sextuple[known]] for known in self.solved } + self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) + self.ideal_basis.discard(0) + + def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, prune=True, algorithm='', output=False): + """ + Solve the the hexagon and pentagon relations to evaluate the F-matrix. + This method (omitting the orthogonality constraints) produces + output in the cyclotomic field, but it is very limited in the size + of examples it can handle: for example, `A_2` at level 2 is + too large for this method. You may use :meth:`find_real_orthogonal_solution` + to solve much larger examples. + + INPUT: + + - ``equations`` -- (optional) a set of equations to be + solved. Defaults to the hexagon and pentagon equations. + - ``factor`` -- (default: ``True``). Set true to use + the sreduce method to simplify the hexagon and pentagon + equations before solving them. + - ``algorithm`` -- (optional). Algorithm to compute Groebner Basis. + - ``output`` -- (optional, default False). Output a dictionary of + F-matrix values. This may be useful to see but may be omitted + since this information will be available afterwards via the + :meth:`fmatrix` and :meth:`fmat` methods. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",1),fusion_label="a") + sage: f.get_solution(verbose=False,output=True) + equations: 8 + equations: 16 + adding equation... fx4 - 1 + {(a2, a2, a2, a0, a1, a1): 1, + (a2, a2, a1, a2, a1, a0): 1, + (a2, a1, a2, a2, a0, a0): 1, + (a2, a1, a1, a1, a0, a2): 1, + (a1, a2, a2, a2, a0, a1): 1, + (a1, a2, a1, a1, a0, a0): 1, + (a1, a1, a2, a1, a2, a0): 1, + (a1, a1, a1, a0, a2, a2): 1} + + After you successfully run ``get_solution`` you may check + the correctness of the F-matrix by running :meth:`hexagon` + and :meth:`pentagon`. These should return empty lists + of equations. In this example, we turn off the factor + and prune optimizations to test all instances. + + EXAMPLES:: + + sage: f.hexagon(factor=False) + equations: 0 + [] + sage: f.hexagon(factor=False,side="right") + equations: 0 + [] + sage: f.pentagon(factor=False,prune=False) + equations: 0 + [] + + """ + if equations is None: + if verbose: + print("Setting up hexagons and pentagons...") + equations = self.get_defining_equations("hexagons")+self.get_defining_equations("pentagons") + #equations = self.hexagon(verbose=False, factor=factor)+self.pentagon(verbose=False, factor=factor, prune=prune) + if verbose: + print("Finding a Groebner basis...") + self.ideal_basis = set(Ideal(equations).groebner_basis(algorithm=algorithm)) + if verbose: + print("Solving...") + self.substitute_degree_one() + if verbose: + print("Fixing the gauge...") + self.fix_gauge(algorithm=algorithm) + if verbose: + print("Done!") + if output: + return self._fvars + ##################### ### Verifications ### ##################### From 33000e164f4a19053153baf96b13a7e4ea38e7b4 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Feb 2021 12:53:02 -0800 Subject: [PATCH 008/414] added support for multiprocessing braid group representations. basic docstring coverage at 100p --- src/sage/combinat/root_system/f_matrix.py | 177 +++++++++----- .../fast_parallel_fmats_methods.pxd | 2 + .../fast_parallel_fmats_methods.pyx | 20 +- src/sage/combinat/root_system/fusion_ring.py | 218 +++++++----------- .../combinat/root_system/poly_tup_engine.pyx | 2 - 5 files changed, 221 insertions(+), 198 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 5bb41b12aea..d83494c3c77 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -171,9 +171,9 @@ class FMatrix(): EXAMPLES:: - sage: f.get_defininig_equations("pentagons")[1:3] + sage: f.get_pentagons()[1:3] [fx1*fx5 - fx7^2, fx5*fx8*fx13 - fx2*fx12] - sage: f.get_defining_equations("hexagons")[1:3] + sage: f.get_hexagons()[1:3] [fx10*fx12 + (-zeta128^32)*fx12*fx13 + (-zeta128^16)*fx12, fx0 - 1] sage: f.get_orthogonality_constraints()[1:3] [fx1^2 - 1, fx2^2 - 1] @@ -483,25 +483,37 @@ def f_to(self,a,b,c,d): #################### def get_fmats_in_alg_field(self): + """ + Return F-symbols as elements of the AlgebraicField. This method uses + self._qqbar_embedding to coerce F-symbols into QQbar. + """ return { sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items() } - #Return radical expression for fmats for easy visualization def get_radical_expression(self): + """ + Return radical expression of F-symbols for easy visualization + """ return { sextuple : val.radical_expression() for sextuple, val in get_fmats_in_alg_field().items() } - #Construct a dictionary of idx, known_val pairs for equation substitution def get_known_vals(self): + """ + Construct a dictionary of idx, known_val pairs for equation substitution + """ return { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in self.solved } - #Construct a dictionary of known squares. Keys are variable indices and corresponding values are the squares def get_known_sq(self,eqns=None): + """ + Construct a dictionary of known squares. Keys are variable indices and corresponding values are the squares + """ if eqns is None: eqns = self.ideal_basis return { variables(eq_tup)[0] : -eq_tup[-1][1] for eq_tup in eqns if tup_fixes_sq(eq_tup) } - #Construct an ETuple indicating positions of known nonzero variables. - #MUST be called after self._ks = get_known_sq() def get_known_nonz(self): + """ + Construct an ETuple indicating positions of known nonzero variables. + MUST be called after self._ks = get_known_sq() + """ nonz = { self._var_to_idx[var] : 100 for var in self._singles } for idx in self._ks: nonz[idx] = 100 @@ -511,25 +523,33 @@ def get_known_nonz(self): ### Useful predicates ### ######################### - #Determine if monomial exponent is univariate in a still unknown F-symbol def is_univariate_in_unknown(self,monom_exp): + """ + Determine if monomial exponent is univariate in a still unknown F-symbol + """ return len(monom_exp.nonzero_values()) == 1 and monom_exp.nonzero_positions()[0] not in self.solved - #Determine if monomial exponent is univariate and linear in a vstill unknown F-symbol def is_uni_linear_in_unkwown(self,monom_exp): + """ + Determine if monomial exponent is univariate and linear in a vstill unknown F-symbol + """ return monom_exp.nonzero_values() == [1] and monom_exp.nonzero_positions()[0] not in self.solved ############################## ### Variables partitioning ### ############################## - #Get the size of the largest F-matrix F^{abc}_d def largest_fmat_size(self): + """ + Get the size of the largest F-matrix F^{abc}_d + """ return max(self.fmatrix(*tup).nrows() for tup in product(self.FR.basis(),repeat=4)) - #Partition the F-symbols according to the size of the F-matrix F^{abc}_d - #they belong to def get_fmats_by_size(self,n): + """ + Partition the F-symbols according to the size of the F-matrix F^{abc}_d + they belong to + """ fvars_copy = deepcopy(self._fvars) solved_copy = deepcopy(self.solved) self.clear_vars() @@ -547,16 +567,23 @@ def get_fmats_by_size(self,n): ### Checkpoint utilities ### ############################ - #Auto-generate filename string def get_fr_str(self): + """ + Auto-generate filename string for saving results + """ return self.FR.cartan_type()[0] + str(self.FR.cartan_type()[1]) + str(self.FR.fusion_level()) def save_fvars(self,filename): + """ + Save current variables state + """ with open(filename, 'wb') as f: pickle.dump([self._fvars, self.solved], f) - #If provided, optional param save_dir should have a trailing forward slash def load_fvars(self,save_dir=""): + """ + If provided, optional param save_dir should have a trailing forward slash + """ with open(save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle", 'rb') as f: self._fvars, self.solved = pickle.load(f) @@ -564,19 +591,23 @@ def load_fvars(self,save_dir=""): ### MapReduce ### ################# - #Apply the given mapper to each element of the given input iterable and - #return the results (with no duplicates) in a list. This method applies the - #mapper in parallel if a worker_pool is provided. - ## INPUT: - #mapper is a string specifying the name of a function defined in the - #fast_parallel_fmats_methods module. - ###NOTES: - #If worker_pool is not provided, function maps and reduces on a single process. - #If worker_pool is provided, the function attempts to determine whether it should - #use multiprocessing based on the length of the input iterable. If it can't determine - #the length of the input iterable then it uses multiprocessing with the default chunksize of 1 - #if chunksize is not explicitly provided. def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): + """ + Apply the given mapper to each element of the given input iterable and + return the results (with no duplicates) in a list. This method applies the + mapper in parallel if a worker_pool is provided. + + # INPUT: + mapper is a string specifying the name of a function defined in the + fast_parallel_fmats_methods module. + + ##NOTES: + If worker_pool is not provided, function maps and reduces on a single process. + If worker_pool is provided, the function attempts to determine whether it should + use multiprocessing based on the length of the input iterable. If it can't determine + the length of the input iterable then it uses multiprocessing with the default chunksize of 1 + if chunksize is not explicitly provided. + """ if mp_thresh is None: mp_thresh = self.mp_thresh #Compute multiprocessing parameters @@ -599,24 +630,16 @@ def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_th if no_mp: results = collect_eqns(0) else: - results = self.reduce_multi_process(collect_eqns,worker_pool) + results = set() + for child_eqns in worker_pool.imap_unordered(collect_eqns,range(worker_pool._processes)): + results.update(child_eqns) + results = list(results) return results - ################ - ### Reducers ### - ################ - - #All-to-one communication step - def reduce_multi_process(self,reducer,worker_pool): - collected_eqns = set() - for child_eqns in worker_pool.imap_unordered(reducer,range(worker_pool._processes)): - collected_eqns.update(child_eqns) - return list(collected_eqns) - ######################## ### Equations set up ### ######################## - + def get_orthogonality_constraints(self,output=True): """ Get equations imposed on the F-matrix by orthogonality. @@ -660,12 +683,16 @@ def get_defining_equations(self,option,worker_pool=None,output=True): ### Equations processing ### ############################ - #Assemble a polynomial object from its tuple representation def tup_to_fpoly(self,eq_tup): + """ + Assemble a polynomial object from its tuple representation + """ return tup_to_poly(eq_tup,parent=self._poly_ring) - #Solve for a linear term occurring in a two-term equation. def solve_for_linear_terms(self,eqns=None): + """ + Solve for a linear term occurring in a two-term equation. + """ if eqns is None: eqns = self.ideal_basis @@ -692,8 +719,10 @@ def solve_for_linear_terms(self,eqns=None): linear_terms_exist = True return linear_terms_exist - #Backward substitution step. Traverse variables in reverse lexicographical order. def backward_subs(self): + """ + Backward substitution step. Traverse variables in reverse lexicographical order. + """ for var in reversed(self._poly_ring.gens()): sextuple = self._var_to_sextuple[var] rhs = self._fvars[sextuple] @@ -702,8 +731,10 @@ def backward_subs(self): kp = compute_known_powers(get_variables_degrees([rhs]), d) self._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp),self._ks).items()) - #Update reduction parameters in all processes def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): + """ + Update reduction parameters in all processes + """ if eqns is None: eqns = self.ideal_basis self._ks, self._var_degs = self.get_known_sq(eqns), get_variables_degrees(eqns) @@ -715,10 +746,11 @@ def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update new_data = [(self._fvars,self.solved,self._ks,self._var_degs)]*n_proc self.map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) - #Perform triangular elimination of linear terms in two-term equations until no such terms exist - #For optimal usage of TRIANGULAR elimination, pass in a SORTED list of equations - #Returns a list of equations def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=True): + """ + Perform triangular elimination of linear terms in two-term equations until no such terms exist + For optimal usage of TRIANGULAR elimination, pass in a SORTED list of equations + """ ret = True if eqns is None: eqns = self.ideal_basis @@ -756,6 +788,15 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T ##################### def equations_graph(self,eqns=None): + """ + Construct a graph corresponding to equations. The nodes in the graph + are indices corresponding to variables in the equations and two + nodes are connected if the corresponding variables appear together in + a given equation. + + If no list of equations is passed, the graph is built from equations in + self.ideal_basis + """ if eqns is None: eqns = self.ideal_basis @@ -769,8 +810,10 @@ def equations_graph(self,eqns=None): G.add_edge(x,y) return G - #Partition equations corresponding to edges in a disconnected graph def partition_eqns(self,graph,eqns=None,verbose=True): + """ + Partition equations corresponding to edges in a disconnected graph + """ if eqns is None: eqns = self.ideal_basis partition = { tuple(c) : [] for c in graph.connected_components() } @@ -781,8 +824,10 @@ def partition_eqns(self,graph,eqns=None,verbose=True): print(graph.connected_components_sizes()) return partition - #Compute a Groebner basis for a set of equations partitioned according to their corresponding graph def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): + """ + Compute a Groebner basis for a set of equations partitioned according to their corresponding graph + """ if eqns is None: eqns = self.ideal_basis graph = self.equations_graph(eqns) small_comps = list() @@ -811,9 +856,11 @@ def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose= ret = small_comp_gb + temp_eqns return ret - #Translate equations in each connected component to smaller polynomial rings - #so we can call built-in variety method. def get_component_variety(self,var,eqns): + """ + Translate equations in each connected component to smaller polynomial rings + so we can call built-in variety method. + """ #Define smaller poly ring in component vars R = PolynomialRing(self.FR.field(),len(var),'a',order='lex') @@ -831,10 +878,12 @@ def get_component_variety(self,var,eqns): ### Solution method ### ####################### - #Get a numeric solution for given equations by using the partitioning the equations - #graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, - #which is computed as a Cartesian product def get_numeric_solution(self,eqns=None,verbose=True): + """ + Get a numeric solution for given equations by using the partitioning the equations + graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, + which is computed as a Cartesian product + """ if eqns is None: eqns = self.ideal_basis eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) @@ -896,10 +945,12 @@ def get_numeric_solution(self,eqns=None,verbose=True): self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } self.clear_equations() - #Solver - #If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash - #Supports "warm" start. Use load_fvars to re-start computation from checkpoint def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): + """ + Solver + If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash + Supports "warm" start. Use load_fvars to re-start computation from checkpoint + """ #Set multiprocessing parameters. Context can only be set once, so we try to set it try: set_start_method('fork') @@ -912,7 +963,6 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): poly_sortkey = cmp_to_key(poly_tup_cmp) self.get_orthogonality_constraints(output=False) self.get_defining_equations('hexagons',worker_pool=pool,output=False) - self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) @@ -1100,12 +1150,16 @@ def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, pr if output: return self._fvars + ##################### ### Verifications ### ##################### - #Ensure the hexagon equations are satisfied + def verify_hexagons(self): + """ + Ensure the hexagon equations are satisfied + """ hex = [] for a,b,c,d,e,g in product(self.FR.basis(),repeat=6): lhs = self.field(self.FR.r_matrix(a,c,e))*self.fmat(a,c,b,d,e,g)*self.field(self.FR.r_matrix(b,c,g)) @@ -1119,6 +1173,9 @@ def verify_hexagons(self): return hex def verify_pentagons(self,use_mp=True,prune=False): + """ + Ensure the pentagon equations are satisfied + """ print("Testing F-symbols for {}...".format(self.FR)) fvars_copy = deepcopy(self._fvars) self._fvars = { sextuple : float(RDF(rhs)) for sextuple, rhs in self.get_fmats_in_alg_field().items() } @@ -1138,8 +1195,10 @@ def verify_pentagons(self,use_mp=True,prune=False): self._fvars = fvars_copy return pe - #Verify that all F-matrices are real and unitary (orthogonal) def fmats_are_orthogonal(self): + """ + Verify that all F-matrices are real and unitary (orthogonal) + """ is_orthog = [] for a,b,c,d in product(self.FR.basis(),repeat=4): mat = self.fmatrix(a,b,c,d) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index 63dde1f57cf..487b7b60e83 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -4,3 +4,5 @@ cpdef update_reduce(factory, tuple eq_tup) cpdef compute_gb(factory, tuple args) cpdef update_child_fmats(factory, tuple data_tup) cpdef pent_verify(factory, tuple mp_params) + +cdef _fmat(factory, a, b, c, d, x, y) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 7add91bdcaf..0f99a504836 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -25,14 +25,16 @@ worker_results = list() ### Parallel code executor ### ############################## -#Execute a function defined in this module (sage.combinat.root_system.fast_parallel_fmats_methods) -#in a worker process, and supply the factory parameter by constructing a reference -#to the FMatrix object in the worker's memory adress space from its id -### NOTE: When the parent process is forked, each worker gets a copy of -#every global variable. The virtual memory address of object X in the parent -#process equals the VIRTUAL memory address of the copy of object X in each -#worker, so we may construct references to forked copies of X def executor(params): + """ + Execute a function defined in this module (sage.combinat.root_system.fast_parallel_fmats_methods) + in a worker process, and supply the factory parameter by constructing a reference + to the FMatrix object in the worker's memory adress space from its id + ## NOTE: When the parent process is forked, each worker gets a copy of + every global variable. The virtual memory address of object X in the parent + process equals the VIRTUAL memory address of the copy of object X in each + worker, so we may construct references to forked copies of X + """ (fn_name, fmats_id), args = params #Construct a reference to global FMatrix object in this worker's memory fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value @@ -44,8 +46,10 @@ def executor(params): ### Mappers ### ############### -#Cython version of fmat class method. Using cdef for fastest dispatch cdef _fmat(factory, a, b, c, d, x, y): + """ + Cython version of fmat class method. Using cdef for fastest dispatch + """ if factory.FR.Nk_ij(a,b,x) == 0 or factory.FR.Nk_ij(x,c,d) == 0 or factory.FR.Nk_ij(b,c,y) == 0 or factory.FR.Nk_ij(a,y,d) == 0: return 0 #Some known zero F-symbols diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 811935103b0..23d19dc4756 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -11,19 +11,19 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.combinat.root_system.weyl_characters import WeylCharacterRing +from itertools import product, zip_longest +from multiprocessing import Pool, set_start_method from sage.combinat.q_analogues import q_int -from sage.matrix.special import diagonal_matrix +import sage.combinat.root_system.f_matrix as FMatrix +from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_eqns, executor +from sage.combinat.root_system.weyl_characters import WeylCharacterRing from sage.matrix.constructor import matrix +from sage.matrix.special import diagonal_matrix +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc import inject_variable from sage.rings.integer_ring import ZZ from sage.rings.number_field.number_field import CyclotomicField -from sage.misc.cachefunc import cached_method - -from itertools import product -import sage.combinat.root_system.f_matrix as FMatrix -from sage.misc.lazy_attribute import lazy_attribute -from sage.matrix.special import diagonal_matrix class FusionRing(WeylCharacterRing): r""" @@ -875,11 +875,13 @@ def D_minus(self): ### Braid group representations ### ################################### - #Recursively enumerate all the admissible trees with given top row and root. - #Returns a list of tuples (l1,...,lk) such that - #root -> lk # m[-1], lk -> l_{k-1} # m[-2], ..., l1 -> m[0] # m[1], - #with top_row = m def get_trees(self,top_row,root): + """ + Recursively enumerate all the admissible trees with given top row and root. + Returns a list of tuples (l1,...,lk) such that + root -> lk # m[-1], lk -> l_{k-1} # m[-2], ..., l1 -> m[0] # m[1], + with top_row = m + """ if len(top_row) == 2: m1, m2 = top_row return [[]] if self.Nk_ij(m1,m2,root) else [] @@ -887,10 +889,12 @@ def get_trees(self,top_row,root): m1, m2 = top_row[:2] return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] - #Get the so-called computational basis for Hom(b, a^n). The basis is a list of - #(n-2)-tuples (m_1,...,m_{n//2},l_1,...,l_{(n-3)//2}) such that - #each m_i is a monomial in a^2 and l_{j+1} \in l_j # a, and l[-1] \in a # b def get_comp_basis(self,a,b,n_strands): + """ + Get the so-called computational basis for Hom(b, a^n). The basis is a list of + (n-2)-tuples (m_1,...,m_{n//2},l_1,...,l_{(n-3)//2}) such that + each m_i is a monomial in a^2 and l_{j+1} \in l_j # a, and l[-1] \in a # b + """ comp_basis = list() for top in product((a*a).monomials(),repeat=n_strands//2): #If the n_strands is odd, we must extend the top row by a fusing anyon @@ -898,143 +902,99 @@ def get_comp_basis(self,a,b,n_strands): comp_basis.extend(tuple([*top,*levels]) for levels in self.get_trees(top_row,b)) return comp_basis - #Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two strands - #in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j - #of trees b -> xj # yj -> (a # a) # (a # a) - @cached_method - def mid_sig_ij(self,row,col,a,b): - xi, yi = row - xj, yj = col - entry = 0 - for c in self.basis(): - for d in self.basis(): - ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) - f1 = self.fmats.fmat(a,a,yi,b,xi,c) - f2 = self.fmats.fmat(a,a,a,c,d,yi) - f3 = self.fmats.fmat(a,a,a,c,d,yj) - f4 = self.fmats.fmat(a,a,yj,b,xj,c) - r = self.r_matrix(a,a,d) - entry += f1 * f2 * r * f3 * f4 - return entry - - #Compute the xi, xj entry of the braid generator on the right-most strands, - #corresponding to the tree b -> (xi # a) -> (a # a) # a, which results in a - #sum over j of trees b -> xj -> (a # a) # (a # a) - @cached_method - def odd_one_out_ij(self,xi,xj,a,b): - entry = 0 - for c in self.basis(): - ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) - f1 = self.fmats.fmat(a,a,a,b,xi,c) - f2 = self.fmats.fmat(a,a,a,b,xj,c) - r = self.r_matrix(a,a,c) - entry += f1 * r * f2 - return entry - @lazy_attribute def fmats(self): + """ + Construct an FMatrix factory to solve the pentagon relations and + organize the resulting F-symbols. We only need this attribute to compute + braid group representations. + """ return FMatrix.FMatrix(self) - #Compute generators of the Artin braid group on n_strands strands. If - #fusing_anyon = a and total_topological_charge = b, the generators are - #endomorphisms of Hom(a^n_strands, b). - ###NOTE: For now, we assume existence of fmats with relevant F-symbols ready. - #In the future this method will call an appropriate F-matrix solver... - #For useful group calculations, the F-symbols should lie in a NumberField. - def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands): - assert n_strands > 2, "The number of strands must be an integer greater than 2" - a, b = fusing_anyon, total_topological_charge + def emap(self,mapper,input_args,worker_pool=None): + """ + Apply the given mapper to each element of the given input iterable and + return the results (with no duplicates) in a list. This method applies the + mapper in parallel if a worker_pool is provided. + + # INPUT: + mapper is a string specifying the name of a function defined in the + fast_parallel_fmats_methods module. + + input_args should be a tuple holding arguments to be passed to mapper + + ##NOTES: + If worker_pool is not provided, function maps and reduces on a single process. + If worker_pool is provided, the function attempts to determine whether it should + use multiprocessing based on the length of the input iterable. If it can't determine + the length of the input iterable then it uses multiprocessing with the default chunksize of 1 + if chunksize is not explicitly provided. + """ + n_proc = worker_pool._processes if worker_pool is not None else 1 + input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] + no_mp = worker_pool is None + #Map phase. Casting Async Object blocks execution... Each process holds results + #in its copy of fmats.temp_eqns + input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) + if no_mp: + list(map(executor,input_iter)) + else: + list(worker_pool.imap_unordered(executor,input_iter,chunksize=1)) + #Reduce phase + if no_mp: + results = collect_eqns(0) + else: + results = set() + for worker_results in worker_pool.imap_unordered(collect_eqns,range(worker_pool._processes),chunksize=1): + results.update(worker_results) + results = list(results) + return results + def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands,use_mp=True): + """ + Compute generators of the Artin braid group on n_strands strands. If + fusing_anyon = a and total_topological_charge = b, the generators are + endomorphisms of Hom(a^n_strands, b) + """ + assert n_strands > 2, "The number of strands must be an integer greater than 2" #Construct associated FMatrix object and solve for F-symbols if not self.fmats.symbols_known: self.fmats.find_real_orthogonal_solution() - #Set up the computational basis + #Set multiprocessing parameters. Context can only be set once, so we try to set it + try: + set_start_method('fork') + except RuntimeError: + pass + pool = Pool() if use_mp else None + + #Set up computational basis and compute generators one at a time + a, b = fusing_anyon, total_topological_charge comp_basis = self.get_comp_basis(a,b,n_strands) - basis_dict = { elt : i for i, elt in enumerate(comp_basis) } - dim = len(comp_basis) - print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(dim,n_strands)) + d = len(comp_basis) + print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) #Compute diagonal odd-indexed generators using the 3j-symbols gens = { 2*i+1 : diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2) } #Compute even-indexed generators using F-matrices for k in range(1,n_strands//2): - entries = dict() - for i in range(dim): - for f,e,q in product(self.basis(),repeat=3): - #Compute appropriate possible nonzero row index - nnz_pos = list(comp_basis[i]) - nnz_pos[k-1:k+1] = f,e - #Handle the special case k = 1 - if k > 1: - nnz_pos[n_strands//2+k-2] = q - nnz_pos = tuple(nnz_pos) - - #Skip repeated entries when k = 1 - if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in entries: - m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] - #A few special cases - top_left = m[0] - if k >= 3: - top_left = l[k-3] - root = b - if k - 1 < len(l): - root = l[k-1] - - #Handle the special case k = 1 - if k == 1: - entries[basis_dict[nnz_pos],i] = self.mid_sig_ij(m[:2],(f,e),a,root) - continue - - entry = 0 - for p in self.basis(): - f1 = self.fmats.fmat(top_left,m[k-1],m[k],root,l[k-2],p) - f2 = self.fmats.fmat(top_left,f,e,root,q,p) - entry += f1 * self.mid_sig_ij((m[k-1],m[k]),(f,e),a,p) * f2 - entries[basis_dict[nnz_pos],i] = entry - gens[2*k] = matrix(entries) + entries = self.emap('sig_2k',(k,a,b,n_strands),pool) + gens[2*k] = matrix(dict(entries)) #If n_strands is odd, we compute the final generator if n_strands % 2: - entries = dict() - for i in range(dim): - for f, q in product(self.basis(),repeat=2): - #Compute appropriate possible nonzero row index - nnz_pos = list(comp_basis[i]) - nnz_pos[n_strands//2-1] = f - #Handle small special case - if n_strands > 3: - nnz_pos[-1] = q - nnz_pos = tuple(nnz_pos) - - if nnz_pos in comp_basis: - m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] - - #Handle a couple of small special cases - if n_strands == 3: - entries[basis_dict[nnz_pos],i] = self.odd_one_out_ij(m[-1],f,a,b) - continue - top_left = m[0] - if n_strands > 5: - top_left = l[-2] - root = b - - #Compute relevant entry - entry = 0 - for p in self.basis(): - f1 = self.fmats.fmat(top_left,m[-1],a,root,l[-1],p) - f2 = self.fmats.fmat(top_left,f,a,root,q,p) - entry += f1 * self.odd_one_out_ij(m[-1],f,a,p) * f2 - entries[basis_dict[nnz_pos],i] = entry - gens[n_strands-1] = matrix(entries) + entries = self.emap('odd_one_out',(a,b,n_strands),pool) + gens[n_strands-1] = matrix(dict(entries)) return comp_basis, [gens[k] for k in sorted(gens)] - #A useful sanity check - #Determine if given iterable of n matrices defines a representation of - #the Artin braid group on (n+1) strands def gens_satisfy_braid_gp_rels(self,sig): + """ + Determine if given iterable of n matrices defines a representation of + the Artin braid group on (n+1) strands. Tests correctness of + get_braid_generators method. + """ n = len(sig) braid_rels = all(sig[i] * sig[i+1] * sig[i] == sig[i+1] * sig[i] * sig[i+1] for i in range(n-1)) far_comm = all(sig[i] * sig[j] == sig[j] * sig[i] for i, j in product(range(n),repeat=2) if abs(i-j) > 1 and i > j) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index f6edc15a235..f74d674619b 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -67,7 +67,6 @@ cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars): ### Convenience methods ### ########################### - cdef ETuple degrees(tuple poly_tup): """ Return the maximal degree of each variable in the polynomial @@ -197,7 +196,6 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): ### Substitution ### #################### -#Pre-compute powers of known values for efficiency cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): """ Pre-compute powers of known values for efficiency From 2e0d1e9986630f074fcf002a63c26e97333fb158 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Feb 2021 14:32:59 -0800 Subject: [PATCH 009/414] begin work on base coercion --- src/sage/combinat/root_system/f_matrix.py | 188 ++++++++++++++---- .../fast_parallel_fmats_methods.pyx | 16 +- src/sage/combinat/root_system/fusion_ring.py | 13 +- .../combinat/root_system/weyl_characters.py | 2 + 4 files changed, 172 insertions(+), 47 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index d83494c3c77..d424dfe8edd 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -27,12 +27,12 @@ import cPickle as pickle except: import pickle -from multiprocessing import cpu_count, Pool, set_start_method +from multiprocessing import cpu_count, Pool, set_start_method, TimeoutError import numpy as np from sage.combinat.root_system.fast_parallel_fmats_methods import * from sage.combinat.root_system.poly_tup_engine import * from sage.rings.polynomial.polydict import ETuple -from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics +from sage.rings.qqbar import AA, QQbar from sage.rings.real_double import RDF from itertools import zip_longest @@ -220,12 +220,12 @@ class FMatrix(): """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): - self.FR = fusion_ring - if self.FR._fusion_labels is None: - self.FR.fusion_labels(fusion_label, inject_variables=True) + self._FR = fusion_ring + if self._FR._fusion_labels is None: + self._FR.fusion_labels(fusion_label, inject_variables=True) #Set up F-symbols entry by entry n_vars = self.findcases() - self._poly_ring = PolynomialRing(self.FR.field(),n_vars,var_prefix) + self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) if inject_variables: print ("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) self._poly_ring.inject_variables() @@ -241,9 +241,9 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.solved = set() #New attributes of the FMatrix class - self.field = self.FR.field() - r = self.field.defining_polynomial().roots(ring=QQbar,multiplicities=False)[0] - self._qqbar_embedding = self.field.hom([r],QQbar) + self._field = self._FR.field() + r = self._field.defining_polynomial().roots(ring=QQbar,multiplicities=False)[0] + self._qqbar_embedding = self._field.hom([r],QQbar) self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } self._ks = self.get_known_sq() self._nnz = self.get_known_nonz() @@ -265,7 +265,7 @@ def __repr__(self): sage: FMatrix(FusionRing("B2",1)) F-Matrix factory for The Fusion Ring of Type B2 and level 1 with Integer Ring coefficients """ - return "F-Matrix factory for %s"%self.FR + return "F-Matrix factory for %s"%self._FR def remaining_vars(self): """ @@ -316,21 +316,21 @@ def fmat(self, a, b, c, d, x, y, data=True): (zeta60^14 - zeta60^6 - zeta60^4 + 1)] """ - if self.FR.Nk_ij(a,b,x) == 0 or self.FR.Nk_ij(x,c,d) == 0 or self.FR.Nk_ij(b,c,y) == 0 or self.FR.Nk_ij(a,y,d) == 0: + if self._FR.Nk_ij(a,b,x) == 0 or self._FR.Nk_ij(x,c,d) == 0 or self._FR.Nk_ij(b,c,y) == 0 or self._FR.Nk_ij(a,y,d) == 0: return 0 #Some known zero F-symbols - if a == self.FR.one(): + if a == self._FR.one(): if x == b and y == d: return 1 else: return 0 - if b == self.FR.one(): + if b == self._FR.one(): if x == a and y == c: return 1 else: return 0 - if c == self.FR.one(): + if c == self._FR.one(): if x == d and y == b: return 1 else: @@ -374,6 +374,12 @@ def fmatrix(self,a,b,c,d): Y = self.f_to(a,b,c,d) return matrix([[self.fmat(a,b,c,d,x,y) for y in Y] for x in X]) + def field(self): + return self._field + + def FR(self): + return self._FR + def findcases(self,output=False): """ Return unknown F-matrix entries. If run with output=True, @@ -402,7 +408,7 @@ def findcases(self,output=False): if output: idx_map = dict() ret = dict() - for (a,b,c,d) in list(product(self.FR.basis(), repeat=4)): + for (a,b,c,d) in list(product(self._FR.basis(), repeat=4)): for x in self.f_from(a, b, c, d): for y in self.f_to(a, b, c, d): fm = self.fmat(a, b, c, d, x, y, data=False) @@ -422,7 +428,7 @@ def singletons(self): Find x_i that are automatically nonzero, because their F-matrix is 1x1 """ ret = [] - for (a, b, c, d) in list(product(self.FR.basis(), repeat=4)): + for (a, b, c, d) in list(product(self._FR.basis(), repeat=4)): (ff,ft) = (self.f_from(a,b,c,d),self.f_to(a,b,c,d)) if len(ff) == 1 and len(ft) == 1: v = self._fvars.get((a,b,c,d,ff[0],ft[0]), None) @@ -451,7 +457,7 @@ def f_from(self,a,b,c,d): [a1, a3] """ - return [x for x in self.FR.basis() if self.FR.Nk_ij(a,b,x) != 0 and self.FR.Nk_ij(x,c,d) != 0] + return [x for x in self._FR.basis() if self._FR.Nk_ij(a,b,x) != 0 and self._FR.Nk_ij(x,c,d) != 0] def f_to(self,a,b,c,d): r""" @@ -476,7 +482,7 @@ def f_to(self,a,b,c,d): """ - return [y for y in self.FR.basis() if self.FR.Nk_ij(b,c,y) != 0 and self.FR.Nk_ij(a,y,d) != 0] + return [y for y in self._FR.basis() if self._FR.Nk_ij(b,c,y) != 0 and self._FR.Nk_ij(a,y,d) != 0] #################### ### Data getters ### @@ -519,6 +525,28 @@ def get_known_nonz(self): nonz[idx] = 100 return ETuple(nonz, self._poly_ring.ngens()) + def get_qqbar_embedding(self): + """ + Return an embedding from the base field containing F-symbols (the + FusionRing's CyclotomicField, a NumberField, or QQbar) into QQbar + """ + return self._qqbar_embedding + + def get_coerce_map_from_fr_cyclotomic_field(self): + """ + Return a coercion map from the FusionRing's cyclotomic field into the + base field containing all F-symbols (this could be the FusionRing's + CyclotomicField, a NumberField, or QQbar). + """ + #If base field is different from associated FusionRing's CyclotomicField, + #return coercion map + try: + return self._coerce_map_from_cyc_field + #Otherwise, return identity map CyclotomicField <-> CyclotomicField + except AttributeError: + F = self._FR.field() + return F.hom([F.gen()], F) + ######################### ### Useful predicates ### ######################### @@ -543,7 +571,7 @@ def largest_fmat_size(self): """ Get the size of the largest F-matrix F^{abc}_d """ - return max(self.fmatrix(*tup).nrows() for tup in product(self.FR.basis(),repeat=4)) + return max(self.fmatrix(*tup).nrows() for tup in product(self._FR.basis(),repeat=4)) def get_fmats_by_size(self,n): """ @@ -554,7 +582,7 @@ def get_fmats_by_size(self,n): solved_copy = deepcopy(self.solved) self.clear_vars() var_set = set() - for quadruple in product(self.FR.basis(),repeat=4): + for quadruple in product(self._FR.basis(),repeat=4): F = self.fmatrix(*quadruple) #Discard trivial 1x1 F-matrix, if applicable if F.nrows() == n and F.coefficients() != [1]: @@ -571,7 +599,7 @@ def get_fr_str(self): """ Auto-generate filename string for saving results """ - return self.FR.cartan_type()[0] + str(self.FR.cartan_type()[1]) + str(self.FR.fusion_level()) + return self._FR.cartan_type()[0] + str(self._FR.cartan_type()[1]) + str(self._FR.fusion_level()) def save_fvars(self,filename): """ @@ -649,7 +677,7 @@ def get_orthogonality_constraints(self,output=True): appended to self.ideal_basis """ eqns = list() - for tup in product(self.FR.basis(), repeat=4): + for tup in product(self._FR.basis(), repeat=4): mat = self.fmatrix(*tup) eqns.extend((mat.T * mat - matrix.identity(mat.nrows())).coefficients()) if output: @@ -768,7 +796,7 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T self.backward_subs() #Support early termination in case only some F-symbols are needed - req_vars_known = all(self._fvars[self._var_to_sextuple[var]] in self.FR.field() for var in required_vars) + req_vars_known = all(self._fvars[self._var_to_sextuple[var]] in self._FR.field() for var in required_vars) if req_vars_known: return 1 #Compute new reduction params, send to child processes if any, and update eqns @@ -862,7 +890,7 @@ def get_component_variety(self,var,eqns): so we can call built-in variety method. """ #Define smaller poly ring in component vars - R = PolynomialRing(self.FR.field(),len(var),'a',order='lex') + R = PolynomialRing(self._FR.field(),len(var),'a',order='lex') #Zip tuples into R and compute Groebner basis idx_map = { old : new for new, old in enumerate(sorted(var)) } @@ -878,7 +906,7 @@ def get_component_variety(self,var,eqns): ### Solution method ### ####################### - def get_numeric_solution(self,eqns=None,verbose=True): + def og_get_numeric_solution(self,eqns=None,verbose=True): """ Get a numeric solution for given equations by using the partitioning the equations graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, @@ -888,11 +916,11 @@ def get_numeric_solution(self,eqns=None,verbose=True): eqns = self.ideal_basis eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) - x = self.FR.field()['x'].gen() + x = self._FR.field()['x'].gen() numeric_fvars = dict() non_cyclotomic_roots = list() must_change_base_field = False - phi = self.FR.field().hom([self.field.gen()],self.FR.field()) + phi = self._FR.field().hom([self._field.gen()],self._FR.field()) for comp, part in eqns_partition.items(): #If component have only one equation in a single variable, get a root if len(comp) == 1 and len(part) == 1: @@ -916,17 +944,96 @@ def get_numeric_solution(self,eqns=None,verbose=True): if must_change_base_field: #If needed, find a NumberField containing all the roots - roots = [self.FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] - self.field, nf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots,minimal=True) + roots = [self._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] + self._field, nf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots,minimal=True) #Embed cyclotomic field into newly constructed NumberField cyc_gen_as_nf_elt = nf_elts.pop(0) - phi = self.FR.field().hom([cyc_gen_as_nf_elt], self.field) + phi = self._FR.field().hom([cyc_gen_as_nf_elt], self._field) numeric_fvars = { k : phi(v) for k, v in numeric_fvars.items() } for i, elt in enumerate(nf_elts): numeric_fvars[non_cyclotomic_roots[i][0]] = elt #Do some appropriate conversions - new_poly_ring = self._poly_ring.change_ring(self.field) + new_poly_ring = self._poly_ring.change_ring(self._field) + nvars = self._poly_ring.ngens() + self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } + self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } + self._poly_ring = new_poly_ring + + #Ensure all F-symbols are known + self.solved.update(numeric_fvars) + nvars = self._poly_ring.ngens() + assert len(self.solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self.solved)]) + + #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) + self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } + for fx, rhs in numeric_fvars.items(): + self._fvars[self._var_to_sextuple[self._poly_ring.gen(fx)]] = ((ETuple({},nvars),rhs),) + self.backward_subs() + self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } + self.clear_equations() + + def get_numeric_solution(self,eqns=None,verbose=True): + """ + Get a numeric solution for given equations by using the partitioning the equations + graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, + which is computed as a Cartesian product + """ + if eqns is None: + eqns = self.ideal_basis + eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) + + F = self._field + x = F['x'].gen() + numeric_fvars = dict() + non_cyclotomic_roots = list() + must_change_base_field = False + phi = F.hom([F.gen()],F) + for comp, part in eqns_partition.items(): + #If component have only one equation in a single variable, get a root + if len(comp) == 1 and len(part) == 1: + #Attempt to find cyclotomic root + univ_poly = tup_to_univ_poly(part[0],x) + real_roots = univ_poly.roots(ring=AA,multiplicities=False) + assert real_roots, "No real solution exists... {} has no real roots".format(univ_poly) + roots = univ_poly.roots(multiplicities=False) + if roots: + numeric_fvars[comp[0]] = roots[0] + else: + non_cyclotomic_roots.append((comp[0],real_roots[0])) + must_change_base_field = True + #Otherwise, compute the component variety and select a point to obtain a numerical solution + else: + sols = self.get_component_variety(comp,part) + assert len(sols) > 1, "No real solution exists... component with variables {} has no real points".format(comp) + for fx, rhs in sols[0].items(): + non_cyclotomic_roots.append((fx,rhs)) + must_change_base_field = True + + if must_change_base_field: + #Attempt to compute smallest number field containing all the F-symbols + #If calculation takes too long, we use QQbar as the base field + proc = Pool(1) + input_args = ((('get_appropriate_number_field',id(self)),non_cyclotomic_roots),) + p = proc.apply_async(executor,input_args) + try: + self._field, bf_elts, self._qqbar_embedding = p.get(timeout=100) + except TimeoutError: + self._field = QQbar + bf_elts = [self._qqbar_embedding(F.gen())] + bf_elts += [rhs for fx,rhs in non_cyclotomic_roots] + self._qqbar_embedding = lambda x : x + + #Embed cyclotomic field into newly constructed base field + cyc_gen_as_bf_elt = bf_elts.pop(0) + phi = self._FR.field().hom([cyc_gen_as_bf_elt], self._field) + self._coerce_map_from_cyc_field = phi + numeric_fvars = { k : phi(v) for k, v in numeric_fvars.items() } + for i, elt in enumerate(bf_elts): + numeric_fvars[non_cyclotomic_roots[i][0]] = elt + + #Do some appropriate conversions + new_poly_ring = self._poly_ring.change_ring(self._field) nvars = self._poly_ring.ngens() self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } @@ -957,7 +1064,7 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): except RuntimeError: pass pool = Pool(processes=max(cpu_count()-1,1)) if use_mp else None - print("Computing F-symbols for {} with {} variables...".format(self.FR, len(self._fvars))) + print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) #Set up hexagon equations and orthogonality constraints poly_sortkey = cmp_to_key(poly_tup_cmp) @@ -1001,6 +1108,7 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): self.get_numeric_solution(verbose=verbose) self.save_fvars(filename) self.symbols_known = True + self._FR._coer = self.get_coerce_map_from_fr_cyclotomic_field() ######################### ### Cyclotomic method ### @@ -1161,12 +1269,12 @@ def verify_hexagons(self): Ensure the hexagon equations are satisfied """ hex = [] - for a,b,c,d,e,g in product(self.FR.basis(),repeat=6): - lhs = self.field(self.FR.r_matrix(a,c,e))*self.fmat(a,c,b,d,e,g)*self.field(self.FR.r_matrix(b,c,g)) - rhs = sum(self.fmat(c,a,b,d,e,f)*self.field(self.FR.r_matrix(f,c,d))*self.fmat(a,b,c,d,f,g) for f in self.FR.basis()) + for a,b,c,d,e,g in product(self._FR.basis(),repeat=6): + lhs = self._field(self._FR.r_matrix(a,c,e))*self.fmat(a,c,b,d,e,g)*self._field(self._FR.r_matrix(b,c,g)) + rhs = sum(self.fmat(c,a,b,d,e,f)*self._field(self._FR.r_matrix(f,c,d))*self.fmat(a,b,c,d,f,g) for f in self._FR.basis()) hex.append(lhs-rhs) - if all(h == self.field.zero() for h in hex): - print("Success!!! Found valid F-symbols for {}".format(self.FR)) + if all(h == self._field.zero() for h in hex): + print("Success!!! Found valid F-symbols for {}".format(self._FR)) else: print("Ooops... something went wrong... These pentagons remain:") print(hex) @@ -1176,7 +1284,7 @@ def verify_pentagons(self,use_mp=True,prune=False): """ Ensure the pentagon equations are satisfied """ - print("Testing F-symbols for {}...".format(self.FR)) + print("Testing F-symbols for {}...".format(self._FR)) fvars_copy = deepcopy(self._fvars) self._fvars = { sextuple : float(RDF(rhs)) for sextuple, rhs in self.get_fmats_in_alg_field().items() } if use_mp: @@ -1187,7 +1295,7 @@ def verify_pentagons(self,use_mp=True,prune=False): params = [(child_id,n_proc) for child_id in range(n_proc)] pe = self.map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) if np.all(np.isclose(np.array(pe),0,atol=1e-7)): - print("Success!!! Found valid F-symbols for {}".format(self.FR)) + print("Success!!! Found valid F-symbols for {}".format(self._FR)) pe = None else: print("Ooops... something went wrong... These pentagons remain:") @@ -1200,7 +1308,7 @@ def fmats_are_orthogonal(self): Verify that all F-matrices are real and unitary (orthogonal) """ is_orthog = [] - for a,b,c,d in product(self.FR.basis(),repeat=4): + for a,b,c,d in product(self._FR.basis(),repeat=4): mat = self.fmatrix(a,b,c,d) is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) return all(is_orthog) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 0f99a504836..ed75a7b7739 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -17,6 +17,7 @@ from sage.combinat.root_system.poly_tup_engine cimport * from sage.rings.ideal import Ideal from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.qqbar import number_field_elements_from_algebraics #Define a global temporary worker results repository worker_results = list() @@ -40,7 +41,7 @@ def executor(params): fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value mod = sage.combinat.root_system.fast_parallel_fmats_methods #Bind module method to FMatrix object in worker process, and call the method - getattr(mod,fn_name)(fmats_obj,args) + return getattr(mod,fn_name)(fmats_obj,args) ############### ### Mappers ### @@ -91,7 +92,8 @@ cpdef get_reduced_hexagons(factory, tuple mp_params): Set up and reduce the hexagon equations corresponding to this worker """ global worker_results - cdef int i, child_id, n_proc + cdef child_id, n_proc + cdef unsigned long i child_id, n_proc = mp_params cdef tuple sextuple for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): @@ -121,8 +123,9 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): Set up and reduce the pentagon equations corresponding to this worker """ global worker_results - cdef int i, child_id, n_proc + cdef int child_id, n_proc child_id, n_proc = mp_params + cdef unsigned long i cdef tuple nonuple cdef MPolynomial_libsingular pe for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): @@ -185,6 +188,11 @@ cpdef update_child_fmats(factory, tuple data_tup): factory._nnz = factory.get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory.get_known_vals()) +def get_appropriate_number_field(factory,non_cyclotomic_roots): + #If needed, find a NumberField containing all the roots + roots = [factory.FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] + return number_field_elements_from_algebraics(roots,minimal=True) + ################ ### Reducers ### ################ @@ -230,7 +238,7 @@ cpdef pent_verify(factory, tuple mp_params): child_id, n_proc = mp_params cdef float t0 cdef tuple nonuple - cdef long i + cdef unsigned long i for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): if i % n_proc == child_id: feq_verif(factory,nonuple) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 23d19dc4756..82512bca9b2 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -790,13 +790,19 @@ def r_matrix(self, i, j, k): if self.Nk_ij(i, j, k) == 0: return 0 if i != j: - return self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2) + ret = self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2) + if self._basecoer is None: + return ret + return self._basecoer(ret) i0 = self.one() B = self.basis() - return sum(y.ribbon()**2 / (i.ribbon() * x.ribbon()**2) + ret = sum(y.ribbon()**2 / (i.ribbon() * x.ribbon()**2) * self.s_ij(i0,y) * self.s_ij(i,z) * self.s_ij(x,z).conjugate() * self.s_ij(k,x).conjugate() * self.s_ij(y,z).conjugate() / self.s_ij(i0,z) for x in B for y in B for z in B) / (self.total_q_order()**4) + if self._basecoer is None: + return ret + return self._basecoer(ret) def global_q_dimension(self): r""" @@ -975,7 +981,8 @@ def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands,us print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) #Compute diagonal odd-indexed generators using the 3j-symbols - gens = { 2*i+1 : diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2) } + phi = self.fmats.get_coerce_map_from_fr_cyclotomic_field() + gens = { 2*i+1 : diagonal_matrix(phi(self.r_matrix(a,a,c[i])) for c in comp_basis) for i in range(n_strands//2) } #Compute even-indexed generators using F-matrices for k in range(1,n_strands//2): diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index 977d191646d..1111689341e 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -121,6 +121,7 @@ def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conju self._base_ring = base_ring self._space = RootSystem(self._cartan_type).ambient_space() self._origin = self._space.zero() + self._coer = None if prefix is None: if ct.is_atomic(): prefix = ct[0]+str(ct[1]) @@ -129,6 +130,7 @@ def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conju self._prefix = prefix self._style = style self._fusion_labels = None + self._basecoer = None self._k = k if ct.is_irreducible(): self._opposition = ct.opposition_automorphism() From e9b9e3779144aaf8370321ee9dfc7b64caaa9b81 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Feb 2021 15:04:43 -0800 Subject: [PATCH 010/414] bugfix: remember .FM -> ._FM --- src/sage/combinat/root_system/f_matrix.py | 2 +- .../fast_parallel_fmats_methods.pyx | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index d424dfe8edd..f307c36cf9d 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -221,7 +221,7 @@ class FMatrix(): """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): self._FR = fusion_ring - if self._FR._fusion_labels is None: + if inject_variables: self._FR.fusion_labels(fusion_label, inject_variables=True) #Set up F-symbols entry by entry n_vars = self.findcases() diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index ed75a7b7739..97b072efd27 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -51,20 +51,20 @@ cdef _fmat(factory, a, b, c, d, x, y): """ Cython version of fmat class method. Using cdef for fastest dispatch """ - if factory.FR.Nk_ij(a,b,x) == 0 or factory.FR.Nk_ij(x,c,d) == 0 or factory.FR.Nk_ij(b,c,y) == 0 or factory.FR.Nk_ij(a,y,d) == 0: + if factory._FR.Nk_ij(a,b,x) == 0 or factory._FR.Nk_ij(x,c,d) == 0 or factory._FR.Nk_ij(b,c,y) == 0 or factory._FR.Nk_ij(a,y,d) == 0: return 0 #Some known zero F-symbols - if a == factory.FR.one(): + if a == factory._FR.one(): if x == b and y == d: return 1 else: return 0 - if b == factory.FR.one(): + if b == factory._FR.one(): if x == a and y == c: return 1 else: return 0 - if c == factory.FR.one(): + if c == factory._FR.one(): if x == d and y == b: return 1 else: @@ -78,10 +78,10 @@ cdef req_cy(factory, tuple sextuple, side="left"): a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = factory.FR.r_matrix(a,c,e)*_fmat(factory,a,c,b,d,e,g)*factory.FR.r_matrix(b,c,g) + lhs = factory._FR.r_matrix(a,c,e)*_fmat(factory,a,c,b,d,e,g)*factory._FR.r_matrix(b,c,g) rhs = 0 - for f in factory.FR.basis(): - rhs += _fmat(factory,c,a,b,d,e,f)*factory.FR.r_matrix(f,c,d)*_fmat(factory,a,b,c,d,f,g) + for f in factory._FR.basis(): + rhs += _fmat(factory,c,a,b,d,e,f)*factory._FR.r_matrix(f,c,d)*_fmat(factory,a,b,c,d,f,g) return lhs-rhs @cython.wraparound(False) @@ -96,7 +96,7 @@ cpdef get_reduced_hexagons(factory, tuple mp_params): cdef unsigned long i child_id, n_proc = mp_params cdef tuple sextuple - for i, sextuple in enumerate(product(factory.FR.basis(),repeat=6)): + for i, sextuple in enumerate(product(factory._FR.basis(),repeat=6)): if i % n_proc == child_id: he = req_cy(factory,sextuple) if he: @@ -111,7 +111,7 @@ cdef MPolynomial_libsingular feq_cy(factory, tuple nonuple, bint prune=False): if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information return factory._poly_ring.zero() cdef rhs = factory._poly_ring.zero() - for h in factory.FR.basis(): + for h in factory._FR.basis(): rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) return lhs - rhs @@ -128,7 +128,7 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): cdef unsigned long i cdef tuple nonuple cdef MPolynomial_libsingular pe - for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): + for i, nonuple in enumerate(product(factory._FR.basis(),repeat=9)): if i % n_proc == child_id: pe = feq_cy(factory,nonuple,prune=prune) if pe: @@ -160,7 +160,7 @@ cpdef compute_gb(factory, tuple args): for fx in variables(eq_tup): sorted_vars.append(fx) sorted_vars = sorted(set(sorted_vars)) - R = PolynomialRing(factory.FR.field(),len(sorted_vars),'a',order=term_order) + R = PolynomialRing(factory._FR.field(),len(sorted_vars),'a',order=term_order) #Zip tuples into R and compute Groebner basis cdef idx_map = dict() @@ -190,7 +190,7 @@ cpdef update_child_fmats(factory, tuple data_tup): def get_appropriate_number_field(factory,non_cyclotomic_roots): #If needed, find a NumberField containing all the roots - roots = [factory.FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] + roots = [factory._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] return number_field_elements_from_algebraics(roots,minimal=True) ################ @@ -221,7 +221,7 @@ cdef feq_verif(factory, tuple nonuple, float tol=5e-8): cdef float diff, lhs, rhs lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) rhs = 0.0 - for h in factory.FR.basis(): + for h in factory._FR.basis(): rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) diff = lhs - rhs if diff > tol or diff < -tol: From d134a72a31963abfc9f377606b6351820c613c00 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Feb 2021 16:44:27 -0800 Subject: [PATCH 011/414] work on base coercions --- src/sage/combinat/root_system/f_matrix.py | 84 +++----------------- src/sage/combinat/root_system/fusion_ring.py | 45 +++++++---- 2 files changed, 41 insertions(+), 88 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index f307c36cf9d..822cc240fa1 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -906,78 +906,12 @@ def get_component_variety(self,var,eqns): ### Solution method ### ####################### - def og_get_numeric_solution(self,eqns=None,verbose=True): + def get_explicit_solution(self,eqns=None,verbose=True): """ - Get a numeric solution for given equations by using the partitioning the equations - graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, - which is computed as a Cartesian product - """ - if eqns is None: - eqns = self.ideal_basis - eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) - - x = self._FR.field()['x'].gen() - numeric_fvars = dict() - non_cyclotomic_roots = list() - must_change_base_field = False - phi = self._FR.field().hom([self._field.gen()],self._FR.field()) - for comp, part in eqns_partition.items(): - #If component have only one equation in a single variable, get a root - if len(comp) == 1 and len(part) == 1: - #Attempt to find cyclotomic root - univ_poly = tup_to_univ_poly(part[0],x) - real_roots = univ_poly.roots(ring=AA,multiplicities=False) - assert real_roots, "No real solution exists... {} has no real roots".format(univ_poly) - roots = univ_poly.roots(multiplicities=False) - if roots: - numeric_fvars[comp[0]] = roots[0] - else: - non_cyclotomic_roots.append((comp[0],real_roots[0])) - must_change_base_field = True - #Otherwise, compute the component variety and select a point to obtain a numerical solution - else: - sols = self.get_component_variety(comp,part) - assert len(sols) > 1, "No real solution exists... component with variables {} has no real points".format(comp) - for fx, rhs in sols[0].items(): - non_cyclotomic_roots.append((fx,rhs)) - must_change_base_field = True - - if must_change_base_field: - #If needed, find a NumberField containing all the roots - roots = [self._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] - self._field, nf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots,minimal=True) - #Embed cyclotomic field into newly constructed NumberField - cyc_gen_as_nf_elt = nf_elts.pop(0) - phi = self._FR.field().hom([cyc_gen_as_nf_elt], self._field) - numeric_fvars = { k : phi(v) for k, v in numeric_fvars.items() } - for i, elt in enumerate(nf_elts): - numeric_fvars[non_cyclotomic_roots[i][0]] = elt - - #Do some appropriate conversions - new_poly_ring = self._poly_ring.change_ring(self._field) - nvars = self._poly_ring.ngens() - self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } - self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } - self._poly_ring = new_poly_ring - - #Ensure all F-symbols are known - self.solved.update(numeric_fvars) - nvars = self._poly_ring.ngens() - assert len(self.solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self.solved)]) - - #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) - self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } - for fx, rhs in numeric_fvars.items(): - self._fvars[self._var_to_sextuple[self._poly_ring.gen(fx)]] = ((ETuple({},nvars),rhs),) - self.backward_subs() - self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } - self.clear_equations() - - def get_numeric_solution(self,eqns=None,verbose=True): - """ - Get a numeric solution for given equations by using the partitioning the equations - graph and selecting an arbitrary point on the discrete degrees-of-freedom variety, - which is computed as a Cartesian product + When this method is called, the solution is already found in + terms of Groeber basis. A few degrees of freedom remain. + By specializing the free variables and back substituting, a solution in + the base field is now obtained. """ if eqns is None: eqns = self.ideal_basis @@ -1059,6 +993,7 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): Supports "warm" start. Use load_fvars to re-start computation from checkpoint """ #Set multiprocessing parameters. Context can only be set once, so we try to set it + self.clear_vars() try: set_start_method('fork') except RuntimeError: @@ -1105,10 +1040,12 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): #Set up new equations graph and compute variety for each component self.ideal_basis = sorted(self.par_graph_gb(term_order="lex"), key=poly_sortkey) self.triangular_elim(verbose=verbose) - self.get_numeric_solution(verbose=verbose) + self.get_explicit_solution(verbose=verbose) self.save_fvars(filename) self.symbols_known = True - self._FR._coer = self.get_coerce_map_from_fr_cyclotomic_field() + self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() + for x in self._FR.basis(): + x.q_dimension.clear_cache() ######################### ### Cyclotomic method ### @@ -1239,6 +1176,7 @@ def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, pr [] """ + self.clear_vars() if equations is None: if verbose: print("Setting up hexagons and pentagons...") diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 82512bca9b2..7f87ca1a9c1 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -467,7 +467,10 @@ def root_of_unity(self, r): """ n = 2 * r * self._cyclotomic_order if n in ZZ: - return self.field().gen() ** n + ret = self.field().gen() ** n + if self._basecoer is None: + return ret + return self._basecoer(ret) else: return None @@ -672,7 +675,7 @@ def Nk_ij(self, elt_i, elt_j, elt_k): return (elt_i * elt_j).monomial_coefficients(copy=False).get(elt_k.weight(), 0) @cached_method - def s_ij(self, elt_i, elt_j): + def s_ij(self, elt_i, elt_j, base_coercion=True): r""" Return the element of the S-matrix of this fusion ring corresponding to the given elements. @@ -699,9 +702,20 @@ def s_ij(self, elt_i, elt_j): [1, -zeta60^14 + zeta60^6 + zeta60^4, -zeta60^14 + zeta60^6 + zeta60^4, -1] """ ijtwist = elt_i.twist() + elt_j.twist() - return sum(k.q_dimension() * self.Nk_ij(elt_i, k, elt_j) + ret = sum(k.q_dimension() * self.Nk_ij(elt_i, k, elt_j) * self.root_of_unity(k.twist() - ijtwist) for k in self.basis()) + if (not base_coercion) or (self._basecoer is None): + return ret + return self._basecoer(ret) + + def s_ijconj(self, elt_i, elt_j, base_coercion=True): + """ + """ + ret = self.s_ij(elt_i, elt_j, base_coercion=False).conjugate() + if (not base_coercion) or (self._basecoer is None): + return ret + return self._basecoer(ret) def s_matrix(self, unitary=False): r""" @@ -790,19 +804,13 @@ def r_matrix(self, i, j, k): if self.Nk_ij(i, j, k) == 0: return 0 if i != j: - ret = self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2) - if self._basecoer is None: - return ret - return self._basecoer(ret) + return self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2) i0 = self.one() B = self.basis() - ret = sum(y.ribbon()**2 / (i.ribbon() * x.ribbon()**2) - * self.s_ij(i0,y) * self.s_ij(i,z) * self.s_ij(x,z).conjugate() - * self.s_ij(k,x).conjugate() * self.s_ij(y,z).conjugate() / self.s_ij(i0,z) + return sum(y.ribbon()**2 / (i.ribbon() * x.ribbon()**2) + * self.s_ij(i0,y) * self.s_ij(i,z) * self.s_ijconj(x,z) + * self.s_ijconj(k,x) * self.s_ijconj(y,z) / self.s_ij(i0,z) for x in B for y in B for z in B) / (self.total_q_order()**4) - if self._basecoer is None: - return ret - return self._basecoer(ret) def global_q_dimension(self): r""" @@ -832,7 +840,10 @@ def total_q_order(self): True """ c = self.virasoro_central_charge() - return self.D_plus() * self.root_of_unity(-c/4) + ret = self.D_plus() * self.root_of_unity(-c/4) + if self._basecoer is None: + return ret + return self._basecoer(ret) def D_plus(self): r""" @@ -1169,4 +1180,8 @@ def q_dimension(self): expr = R(expr) expr = expr.substitute(q=q**4) / (q**(2*expr.degree())) zet = P.field().gen() ** (P._cyclotomic_order/P._l) - return expr.substitute(q=zet) + ret = expr.substitute(q=zet) + if self.parent()._basecoer is None: + return ret + return self.parent()._basecoer(ret) + From b92197c60da31f434487377e521b832a4cedba6f Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sat, 27 Feb 2021 12:32:05 -0800 Subject: [PATCH 012/414] clear equations in find_real_orthogonal_solution --- src/sage/combinat/root_system/f_matrix.py | 2 ++ src/sage/combinat/root_system/fusion_ring.py | 4 +++- src/sage/combinat/root_system/weyl_characters.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 822cc240fa1..f285c8807f1 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -993,6 +993,7 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): Supports "warm" start. Use load_fvars to re-start computation from checkpoint """ #Set multiprocessing parameters. Context can only be set once, so we try to set it + self.clear_equations() self.clear_vars() try: set_start_method('fork') @@ -1043,6 +1044,7 @@ def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): self.get_explicit_solution(verbose=verbose) self.save_fvars(filename) self.symbols_known = True + self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() for x in self._FR.basis(): x.q_dimension.clear_cache() diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 7f87ca1a9c1..c7f90c2aea5 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -447,7 +447,9 @@ def field(self): sage: FusionRing("B2",2).field() Cyclotomic Field of order 40 and degree 16 """ - return CyclotomicField(4 * self._cyclotomic_order) + if self._field is None: + self._field = CyclotomicField(4 * self._cyclotomic_order) + return self._field def root_of_unity(self, r): r""" diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index 1111689341e..0a1e8c18fd8 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -121,7 +121,6 @@ def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conju self._base_ring = base_ring self._space = RootSystem(self._cartan_type).ambient_space() self._origin = self._space.zero() - self._coer = None if prefix is None: if ct.is_atomic(): prefix = ct[0]+str(ct[1]) @@ -130,6 +129,7 @@ def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conju self._prefix = prefix self._style = style self._fusion_labels = None + self._field = None self._basecoer = None self._k = k if ct.is_irreducible(): From 54bb6b6a2aa17f71b6a1def43e610f592442dad4 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sun, 28 Feb 2021 10:33:03 -0800 Subject: [PATCH 013/414] base coercion debugging --- src/sage/combinat/root_system/f_matrix.py | 2 + .../fast_parallel_fmats_methods.pyx | 4 +- src/sage/combinat/root_system/fusion_ring.py | 82 +++++++++++-------- 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index f285c8807f1..6926e950721 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -283,6 +283,7 @@ def clear_vars(self): """ Clear the set of variables. """ + self._FR._basecoer = None self._fvars = { self._var_to_sextuple[key] : key for key in self._var_to_sextuple } self.solved = set() @@ -1179,6 +1180,7 @@ def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, pr """ self.clear_vars() + self.clear_equations() if equations is None: if verbose: print("Setting up hexagons and pentagons...") diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 97b072efd27..5f2bc0c3f18 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -78,10 +78,10 @@ cdef req_cy(factory, tuple sextuple, side="left"): a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = factory._FR.r_matrix(a,c,e)*_fmat(factory,a,c,b,d,e,g)*factory._FR.r_matrix(b,c,g) + lhs = factory._FR.r_matrix(a,c,e,base_coercion=False)*_fmat(factory,a,c,b,d,e,g)*factory._FR.r_matrix(b,c,g,base_coercion=False) rhs = 0 for f in factory._FR.basis(): - rhs += _fmat(factory,c,a,b,d,e,f)*factory._FR.r_matrix(f,c,d)*_fmat(factory,a,b,c,d,f,g) + rhs += _fmat(factory,c,a,b,d,e,f)*factory._FR.r_matrix(f,c,d,base_coercion=False)*_fmat(factory,a,b,c,d,f,g) return lhs-rhs @cython.wraparound(False) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index c7f90c2aea5..56e8be8eadb 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -360,7 +360,7 @@ def _test_total_q_order(self, **options): sage: G22._test_total_q_order() """ tester = self._tester(**options) - tqo = self.total_q_order() + tqo = self.total_q_order(base_coercion=False) tester.assertTrue(tqo.is_real_positive()) tester.assertEqual(tqo**2, self.global_q_dimension()) @@ -451,7 +451,7 @@ def field(self): self._field = CyclotomicField(4 * self._cyclotomic_order) return self._field - def root_of_unity(self, r): + def root_of_unity(self, r, base_coercion=True): r""" Return `e^{i\pi r}` as an element of ``self.field()`` if possible. @@ -470,7 +470,7 @@ def root_of_unity(self, r): n = 2 * r * self._cyclotomic_order if n in ZZ: ret = self.field().gen() ** n - if self._basecoer is None: + if (not base_coercion) or (self._basecoer is None): return ret return self._basecoer(ret) else: @@ -704,8 +704,8 @@ def s_ij(self, elt_i, elt_j, base_coercion=True): [1, -zeta60^14 + zeta60^6 + zeta60^4, -zeta60^14 + zeta60^6 + zeta60^4, -1] """ ijtwist = elt_i.twist() + elt_j.twist() - ret = sum(k.q_dimension() * self.Nk_ij(elt_i, k, elt_j) - * self.root_of_unity(k.twist() - ijtwist) + ret = sum(k.q_dimension(base_coercion=False) * self.Nk_ij(elt_i, k, elt_j) + * self.root_of_unity(k.twist() - ijtwist, base_coercion=False) for k in self.basis()) if (not base_coercion) or (self._basecoer is None): return ret @@ -719,7 +719,7 @@ def s_ijconj(self, elt_i, elt_j, base_coercion=True): return ret return self._basecoer(ret) - def s_matrix(self, unitary=False): + def s_matrix(self, unitary=False, base_coercion=True): r""" Return the S-matrix of this fusion ring. @@ -751,14 +751,14 @@ def s_matrix(self, unitary=False): [0 0 0 1] """ b = self.basis() - S = matrix([[self.s_ij(b[x], b[y]) for x in self.get_order()] for y in self.get_order()]) + S = matrix([[self.s_ij(b[x], b[y], base_coercion=base_coercion) for x in self.get_order()] for y in self.get_order()]) if unitary: - return S / self.total_q_order() + return S / self.total_q_order(base_coercion=base_coercion) else: return S @cached_method - def r_matrix(self, i, j, k): + def r_matrix(self, i, j, k, base_coercion=True): r""" Return the R-matrix entry corresponding to the subobject ``k`` in the tensor product of ``i`` with ``j``. @@ -804,19 +804,23 @@ def r_matrix(self, i, j, k): True """ if self.Nk_ij(i, j, k) == 0: - return 0 + ret = 0 if i != j: - return self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2) - i0 = self.one() - B = self.basis() - return sum(y.ribbon()**2 / (i.ribbon() * x.ribbon()**2) - * self.s_ij(i0,y) * self.s_ij(i,z) * self.s_ijconj(x,z) - * self.s_ijconj(k,x) * self.s_ijconj(y,z) / self.s_ij(i0,z) - for x in B for y in B for z in B) / (self.total_q_order()**4) + ret = self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2, base_coercion=False) + else: + i0 = self.one() + B = self.basis() + return sum(y.ribbon(base_coercion=False)**2 / (i.ribbon(base_coercion=False) * x.ribbon(base_coercion=False)**2) + * self.s_ij(i0,y,base_coercion=False) * self.s_ij(i,z,base_coercion=False) * self.s_ijconj(x,z,base_coercion=False) + * self.s_ijconj(k,x,base_coercion=False) * self.s_ijconj(y,z,base_coercion=False) / self.s_ij(i0,z,base_coercion=False) + for x in B for y in B for z in B) / (self.total_q_order(base_coercion=False)**4) + if (not base_coercion) or (self._basecoer is None): + return ret + return self._basecoer(ret) - def global_q_dimension(self): + def global_q_dimension(self, base_coercion=True): r""" - Return `\sum d_i^2`, where the sum is over all simple objects + Return `\sum d_i^2`, where the sum is over all simple objects and `d_i` is the quantum dimension. It is a positive real number. EXAMPLES:: @@ -824,9 +828,12 @@ def global_q_dimension(self): sage: FusionRing("E6",1).global_q_dimension() 3 """ - return sum(x.q_dimension()**2 for x in self.basis()) + ret = sum(x.q_dimension(base_coercion=False)**2 for x in self.basis()) + if (not base_coercion) or (self._basecoer is None): + return ret + return self._basecoer(ret) - def total_q_order(self): + def total_q_order(self, base_coercion=True): r""" Return the positive square root of ``self.global_q_dimension()`` as an element of ``self.field()``. @@ -842,12 +849,12 @@ def total_q_order(self): True """ c = self.virasoro_central_charge() - ret = self.D_plus() * self.root_of_unity(-c/4) - if self._basecoer is None: + ret = self.D_plus(base_coercion=False) * self.root_of_unity(-c/4,base_coercion=False) + if (not base_coercion) or (self._basecoer is None): return ret return self._basecoer(ret) - def D_plus(self): + def D_plus(self, base_coercion=True): r""" Return `\sum d_i^2\theta_i` where `i` runs through the simple objects, `d_i` is the quantum dimension and `\theta_i` is the twist. @@ -868,9 +875,13 @@ def D_plus(self): sage: Dp/Dm == B31.root_of_unity(c/2) True """ - return sum((x.q_dimension())**2 * x.ribbon() for x in self.basis()) + ret = sum((x.q_dimension(base_coercion=False))**2 * x.ribbon(base_coercion=False) for x in self.basis()) + if (not base_coercion) or (self._basecoer is None): + return ret + return self._basecoer(ret) + - def D_minus(self): + def D_minus(self, base_coercion=True): r""" Return `\sum d_i^2\theta_i^{-1}` where `i` runs through the simple objects, `d_i` is the quantum dimension and `\theta_i` is the twist. @@ -888,8 +899,11 @@ def D_minus(self): sage: Dp*Dm == E83.global_q_dimension() True """ - return sum((x.q_dimension())**2 / x.ribbon() for x in self.basis()) - + ret = sum((x.q_dimension(base_coercion=False))**2 / x.ribbon(base_coercion=False) for x in self.basis()) + if (not base_coercion) or (self._basecoer is None): + return ret + return self._basecoer(ret) + ################################### ### Braid group representations ### ################################### @@ -1114,7 +1128,7 @@ def twist(self, reduced=True): else: return twist - def ribbon(self): + def ribbon(self,base_coercion=True): r""" Return the twist or ribbon element of ``self``. @@ -1135,10 +1149,13 @@ def ribbon(self): sage: [F.root_of_unity(x) for x in [0, 3/10, 4/5, 3/2]] [1, zeta40^6, zeta40^12 - zeta40^8 + zeta40^4 - 1, -zeta40^10] """ - return self.parent().root_of_unity(self.twist()) + ret = self.parent().root_of_unity(self.twist(),base_coercion=False) + if (not base_coercion) or (self.parent()._basecoer is None): + return ret + return self.parent()._basecoer(ret) @cached_method - def q_dimension(self): + def q_dimension(self, base_coercion=True): r""" Return the quantum dimension as an element of the cyclotomic field of the `2\ell`-th roots of unity, where `l = m (k+h^\vee)` @@ -1183,7 +1200,8 @@ def q_dimension(self): expr = expr.substitute(q=q**4) / (q**(2*expr.degree())) zet = P.field().gen() ** (P._cyclotomic_order/P._l) ret = expr.substitute(q=zet) - if self.parent()._basecoer is None: + + if (not base_coercion) or (self.parent()._basecoer is None): return ret return self.parent()._basecoer(ret) From d94a214cdcb75d6322b5656073113bed232e23e4 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sun, 28 Feb 2021 14:57:43 -0800 Subject: [PATCH 014/414] two missing files --- .../fast_parallel_fusion_ring_braid_repn.pxd | 5 + .../fast_parallel_fusion_ring_braid_repn.pyx | 213 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd create mode 100644 src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd new file mode 100644 index 00000000000..7f4c49b63cd --- /dev/null +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd @@ -0,0 +1,5 @@ +cpdef mid_sig_ij(fusion_ring,row,col,a,b) +cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b) + +cpdef sig_2k(fusion_ring, tuple args) +cpdef odd_one_out(fusion_ring, tuple args) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx new file mode 100644 index 00000000000..9ecc49bc084 --- /dev/null +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -0,0 +1,213 @@ +""" +Fast FusionRing methods for computing braid group representations +""" +# **************************************************************************** +# Copyright (C) 2021 Guillermo Aboumrad +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + +cimport cython +import ctypes +from itertools import product +import sage +from sage.combinat.root_system.fast_parallel_fmats_methods cimport _fmat +from sage.misc.cachefunc import cached_function + +#Define a global temporary worker results repository +worker_results = list() + +############################## +### Parallel code executor ### +############################## + +def executor(params): + """ + Execute a function defined in this module (sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn) + in a worker process, and supply the factory parameter by constructing a reference + to the FMatrix object in the worker's memory adress space from its id + ## NOTE: When the parent process is forked, each worker gets a copy of + every global variable. The virtual memory address of object X in the parent + process equals the VIRTUAL memory address of the copy of object X in each + worker, so we may construct references to forked copies of X + """ + (fn_name, fr_id), args = params + #Construct a reference to global FMatrix object in this worker's memory + fusion_ring_obj = ctypes.cast(fr_id, ctypes.py_object).value + mod = sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn + #Bind module method to FMatrix object in worker process, and call the method + getattr(mod,fn_name)(fusion_ring_obj,args) + +############### +### Mappers ### +############### + +cpdef mid_sig_ij(fusion_ring,row,col,a,b): + """ + Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two strands + in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j + of trees b -> xj # yj -> (a # a) # (a # a) + """ + xi, yi = row + xj, yj = col + entry = 0 + phi = fusion_ring.fmats.get_coerce_map_from_fr_cyclotomic_field() + for c in fusion_ring.basis(): + for d in fusion_ring.basis(): + ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) + f1 = _fmat(fusion_ring.fmats,a,a,yi,b,xi,c) + f2 = _fmat(fusion_ring.fmats,a,a,a,c,d,yi) + f3 = _fmat(fusion_ring.fmats,a,a,a,c,d,yj) + f4 = _fmat(fusion_ring.fmats,a,a,yj,b,xj,c) + r = fusion_ring.r_matrix(a,a,d) + if not phi.is_identity(): + r = phi(r) + entry += f1 * f2 * r * f3 * f4 + return entry + +cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): + """ + Compute the xi, xj entry of the braid generator on the right-most strands, + corresponding to the tree b -> (xi # a) -> (a # a) # a, which results in a + sum over j of trees b -> xj -> (a # a) # (a # a) + """ + entry = 0 + phi = fusion_ring.fmats.get_coerce_map_from_fr_cyclotomic_field() + for c in fusion_ring.basis(): + ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) + f1 = _fmat(fusion_ring.fmats,a,a,a,b,xi,c) + f2 = _fmat(fusion_ring.fmats,a,a,a,b,xj,c) + r = fusion_ring.r_matrix(a,a,c) + if not phi.is_identity(): + r = phi(r) + entry += f1 * r * f2 + return entry + +#Cache methods +mid_sig_ij = cached_function(mid_sig_ij, name='mid_sig_ij') +odd_one_out_ij = cached_function(odd_one_out_ij, name='odd_one_out_ij') + +@cython.wraparound(False) +@cython.nonecheck(False) +@cython.cdivision(True) +cpdef sig_2k(fusion_ring, tuple args): + """ + Compute entries of the 2k-th braid generator + """ + child_id, n_proc, fn_args = args + k, a, b, n_strands = fn_args + cdef int ctr = -1 + global worker_results + #Get computational basis + cdef list comp_basis = fusion_ring.get_comp_basis(a,b,n_strands) + cdef dict basis_dict = { elt : i for i, elt in enumerate(comp_basis) } + cdef int dim = len(comp_basis) + cdef set entries = set() + cdef int i + for i in range(dim): + for f,e,q in product(fusion_ring.basis(),repeat=3): + #Distribute work amongst processes + ctr += 1 + if ctr % n_proc != child_id: + continue + + #Compute appropriate possible nonzero row index + nnz_pos = list(comp_basis[i]) + nnz_pos[k-1:k+1] = f,e + #Handle the special case k = 1 + if k > 1: + nnz_pos[n_strands//2+k-2] = q + nnz_pos = tuple(nnz_pos) + + #Skip repeated entries when k = 1 + if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in entries: + m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] + #A few special cases + top_left = m[0] + if k >= 3: + top_left = l[k-3] + root = b + if k - 1 < len(l): + root = l[k-1] + + #Handle the special case k = 1 + if k == 1: + entry = mid_sig_ij(fusion_ring,m[:2],(f,e),a,root) + worker_results.append(((basis_dict[nnz_pos],i), entry)) + continue + + entry = 0 + for p in fusion_ring.basis(): + f1 = _fmat(fusion_ring.fmats,top_left,m[k-1],m[k],root,l[k-2],p) + f2 = _fmat(fusion_ring.fmats,top_left,f,e,root,q,p) + entry += f1 * mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 + worker_results.append(((basis_dict[nnz_pos],i), entry)) + entries.add((basis_dict[nnz_pos],i)) + +@cython.wraparound(False) +@cython.nonecheck(False) +@cython.cdivision(True) +cpdef odd_one_out(fusion_ring, tuple args): + """ + Compute entries of the rightmost braid generator, in case we have an odd number + of strands + """ + global worker_results + child_id, n_proc, fn_args = args + a, b, n_strands = fn_args + cdef ctr = -1 + #Get computational basis + comp_basis = fusion_ring.get_comp_basis(a,b,n_strands) + basis_dict = { elt : i for i, elt in enumerate(comp_basis) } + dim = len(comp_basis) + for i in range(dim): + for f, q in product(fusion_ring.basis(),repeat=2): + #Distribute work amongst processes + ctr += 1 + if ctr % n_proc != child_id: + continue + + #Compute appropriate possible nonzero row index + nnz_pos = list(comp_basis[i]) + nnz_pos[n_strands//2-1] = f + #Handle small special case + if n_strands > 3: + nnz_pos[-1] = q + nnz_pos = tuple(nnz_pos) + + if nnz_pos in comp_basis: + m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] + + #Handle a couple of small special cases + if n_strands == 3: + entry = odd_one_out_ij(fusion_ring,m[-1],f,a,b) + worker_results.append(((basis_dict[nnz_pos],i), entry)) + continue + top_left = m[0] + if n_strands > 5: + top_left = l[-2] + root = b + + #Compute relevant entry + entry = 0 + for p in fusion_ring.basis(): + f1 = _fmat(fusion_ring.fmats,top_left,m[-1],a,root,l[-1],p) + f2 = _fmat(fusion_ring.fmats,top_left,f,a,root,q,p) + entry += f1 * odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 + worker_results.append(((basis_dict[nnz_pos],i), entry)) + +################ +### Reducers ### +################ + +def collect_eqns(proc): + """ + Helper function for returning processed results back to parent process. + Trivial reducer: simply collects objects with the same key in the worker + """ + #Discard the zero polynomial + global worker_results + reduced = set(worker_results)-set([tuple()]) + worker_results = list() + return list(reduced) From 6cb376ac3d1250968420f974b16f70e85beeb882 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sun, 28 Feb 2021 15:06:48 -0800 Subject: [PATCH 015/414] get_main_globals() to inject variables in FMatrix --- src/sage/combinat/root_system/f_matrix.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 6926e950721..8c5862240fb 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -20,6 +20,7 @@ from sage.misc.misc import inject_variable from sage.rings.polynomial.all import PolynomialRing from sage.rings.ideal import Ideal +from sage.misc.misc import get_main_globals from copy import deepcopy #Import pickle for checkpointing and loading certain variables @@ -228,9 +229,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) if inject_variables: print ("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) - self._poly_ring.inject_variables() - # for i in range(self._poly_ring.ngens()): - # inject_variable("%s%s"%(var_prefix,i),self._poly_ring.gens()[i]) + self._poly_ring.inject_variables(get_main_globals()) self._var_to_sextuple, self._fvars = self.findcases(output=True) self._singles = self.singletons() From ad5519952db746b1f195a57bbb1c9b859c4212e0 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Tue, 2 Mar 2021 08:05:20 -0800 Subject: [PATCH 016/414] refuse to create FMatrix in non-multiplicity-free case --- src/sage/combinat/root_system/f_matrix.py | 8 ++++--- src/sage/combinat/root_system/fusion_ring.py | 25 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 8c5862240fb..a284d201f8d 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -222,8 +222,10 @@ class FMatrix(): """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): self._FR = fusion_ring - if inject_variables: + if inject_variables and self._FR.fusion_labels is None: self._FR.fusion_labels(fusion_label, inject_variables=True) + if not self._FR.is_multiplicity_free(): + raise ValueError("FMatrix is only available for multiplicity free FusionRings") #Set up F-symbols entry by entry n_vars = self.findcases() self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) @@ -986,7 +988,7 @@ def get_explicit_solution(self,eqns=None,verbose=True): self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } self.clear_equations() - def find_real_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): + def find_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): """ Solver If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash @@ -1246,7 +1248,7 @@ def verify_pentagons(self,use_mp=True,prune=False): def fmats_are_orthogonal(self): """ - Verify that all F-matrices are real and unitary (orthogonal) + Verify that all F-matrices are orthogonal """ is_orthog = [] for a,b,c,d in product(self._FR.basis(),repeat=4): diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 56e8be8eadb..e5759fd38f2 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -904,6 +904,31 @@ def D_minus(self, base_coercion=True): return ret return self._basecoer(ret) + def is_multiplicity_free(self): + """ + Return ``True`` if the fusion multiplicities + :meth:`Nk_ij` are bounded by 1. The :class:`FMatrix` + is available only for multiplicity free :class:`FusionRing`s. + + EXAMPLES:: + + sage: [FusionRing(ct,k).is_multiplicity_free() for ct in ("A1","A2","B2","C3") for k in (1,2,3)] + [True, True, True, True, True, False, True, True, False, True, False, False] + + """ + ct = self.cartan_type() + k = self.fusion_level() + if ct.letter == 'A': + if ct.n == 1: + return True + return k <= 2 + if ct.letter in ['B','D','G','F','E']: + return k <= 2 + if ct.letter == 'C': + if ct.n == 2: + return k <= 2 + return k == 1 + ################################### ### Braid group representations ### ################################### From b664aeddc9d3ba40c86118456b042411e8f2a639 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:26:21 -0800 Subject: [PATCH 017/414] bugfix r-matrix method --- src/sage/combinat/root_system/fusion_ring.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index e5759fd38f2..4247c1e3df5 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -804,13 +804,13 @@ def r_matrix(self, i, j, k, base_coercion=True): True """ if self.Nk_ij(i, j, k) == 0: - ret = 0 + return 0 if i != j: ret = self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2, base_coercion=False) else: i0 = self.one() B = self.basis() - return sum(y.ribbon(base_coercion=False)**2 / (i.ribbon(base_coercion=False) * x.ribbon(base_coercion=False)**2) + ret = sum(y.ribbon(base_coercion=False)**2 / (i.ribbon(base_coercion=False) * x.ribbon(base_coercion=False)**2) * self.s_ij(i0,y,base_coercion=False) * self.s_ij(i,z,base_coercion=False) * self.s_ijconj(x,z,base_coercion=False) * self.s_ijconj(k,x,base_coercion=False) * self.s_ijconj(y,z,base_coercion=False) / self.s_ij(i0,z,base_coercion=False) for x in B for y in B for z in B) / (self.total_q_order(base_coercion=False)**4) From 2e6e804463a16ef5614271e9495c297e2a0a4cb0 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sat, 6 Mar 2021 11:05:37 -0800 Subject: [PATCH 018/414] doctest revision in f_matrix.py, fusion_label flexibility for FusionRings --- src/sage/combinat/root_system/f_matrix.py | 156 +++++++++--------- src/sage/combinat/root_system/fusion_ring.py | 12 +- .../combinat/root_system/weyl_characters.py | 9 +- 3 files changed, 95 insertions(+), 82 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index a284d201f8d..53d2ebefa1a 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -21,13 +21,14 @@ from sage.rings.polynomial.all import PolynomialRing from sage.rings.ideal import Ideal from sage.misc.misc import get_main_globals - from copy import deepcopy + #Import pickle for checkpointing and loading certain variables try: import cPickle as pickle except: import pickle + from multiprocessing import cpu_count, Pool, set_start_method, TimeoutError import numpy as np from sage.combinat.root_system.fast_parallel_fmats_methods import * @@ -167,46 +168,41 @@ class FMatrix(): definition of a monoidal category. The hexagon relations reflect the axioms of a *braided monoidal category*, which are constraints on both the F-matrix and on - the R-matrix. Finally, orthogonality constraints - may be imposed to obtain a unitary F-matrix. + the R-matrix. Optionally, orthogonality constraints + may be imposed to obtain an orthogonal F-matrix. EXAMPLES:: - sage: f.get_pentagons()[1:3] + sage: f.get_defining_equations("pentagons")[1:3] [fx1*fx5 - fx7^2, fx5*fx8*fx13 - fx2*fx12] - sage: f.get_hexagons()[1:3] + sage: f.get_defining_equations("hexagons")[1:3] [fx10*fx12 + (-zeta128^32)*fx12*fx13 + (-zeta128^16)*fx12, fx0 - 1] sage: f.get_orthogonality_constraints()[1:3] [fx1^2 - 1, fx2^2 - 1] - You may solve these 41+14=55 equations to compute the F-matrix. + There are two methods available to compute an F-matrix. + The first, :meth:`find_cyclotomic_solution` uses only + the pentagon and hexagon relations. The second, + :meth:`find_orthogonal_solution` uses additionally + the orthogonality relations. There are some differences + that should be kept in mind. + + :meth:`find_cyclotomic_solution` currently works only with + smaller examples. For example the FusionRing for A2 at + level 2 is too large. When it is available, this method + produces an F-matrix whose entries are in the same + cyclotomic field as the underlying FusionRing. EXAMPLES:: - sage: f.get_solution(output=True) + sage: f.find_cyclotomic_solution() Setting up hexagons and pentagons... - equations: 14 - equations: 37 Finding a Groebner basis... Solving... Fixing the gauge... adding equation... fx1 - 1 adding equation... fx11 - 1 Done! - {(s, s, s, s, i0, i0): (-1/2*zeta128^48 + 1/2*zeta128^16), - (s, s, s, s, i0, p): 1, - (s, s, s, s, p, i0): 1/2, - (s, s, s, s, p, p): (1/2*zeta128^48 - 1/2*zeta128^16), - (s, s, p, i0, p, s): (-1/2*zeta128^48 + 1/2*zeta128^16), - (s, s, p, p, i0, s): (-zeta128^48 + zeta128^16), - (s, p, s, i0, s, s): 1, - (s, p, s, p, s, s): -1, - (s, p, p, s, s, i0): 1, - (p, s, s, i0, s, p): (-zeta128^48 + zeta128^16), - (p, s, s, p, s, i0): (-1/2*zeta128^48 + 1/2*zeta128^16), - (p, s, p, s, s, s): -1, - (p, p, s, s, i0, s): 1, - (p, p, p, p, i0, i0): 1} We now have access to the values of the F-mstrix using the methods :meth:`fmatrix` and :meth:`fmat`. @@ -219,10 +215,20 @@ class FMatrix(): sage: f.fmat(s,s,s,s,p,p) (1/2*zeta128^48 - 1/2*zeta128^16) + :meth:`find_orthogonal_solution` is much more powerful + and is capable of handling large cases, sometimes + quickly but sometimes (in larger cases) hours of + computation. Its F-matrices are not always in the + cyclotomic field that is the base ring of the underlying + FusionRing, but sometimes in an extension field adjoining + some square roots. When this happens, the FusionRing is + modified, adding an attribute :attr:`_basecoer that is + a coercion from the cyclotomic field to the F-matrix. + """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): self._FR = fusion_ring - if inject_variables and self._FR.fusion_labels is None: + if inject_variables and (self._FR._fusion_labels is None): self._FR.fusion_labels(fusion_label, inject_variables=True) if not self._FR.is_multiplicity_free(): raise ValueError("FMatrix is only available for multiplicity free FusionRings") @@ -294,29 +300,26 @@ def fmat(self, a, b, c, d, x, y, data=True): EXAMPLES:: - sage: f=FMatrix(FusionRing("G2",1),fusion_label=["i0","t"]) - sage: [f.fmat(t,t,t,t,x,y) for x in f.FR.basis() for y in f.FR.basis()] + sage: f=FMatrix(FusionRing("G2",1,fusion_labels=("i0","t"),inject_variables=True)) + sage: [f.fmat(t,t,t,t,x,y) for x in f._FR.basis() for y in f._FR.basis()] [fx1, fx2, fx3, fx4] - sage: f.get_solution(output=True) + sage: f.find_cyclotomic_solution(output=True) Setting up hexagons and pentagons... - equations: 5 - equations: 10 Finding a Groebner basis... Solving... Fixing the gauge... adding equation... fx2 - 1 Done! {(t, t, t, i0, t, t): 1, - (t, t, t, t, i0, i0): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), - (t, t, t, t, i0, t): 1, - (t, t, t, t, t, i0): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), - (t, t, t, t, t, t): (zeta60^14 - zeta60^6 - zeta60^4 + 1)} - sage: [f.fmat(t,t,t,t,x,y) for x in f.FR.basis() for y in f.FR.basis()] + (t, t, t, t, i0, i0): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), + (t, t, t, t, i0, t): 1, + (t, t, t, t, t, i0): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), + (t, t, t, t, t, t): (zeta60^14 - zeta60^6 - zeta60^4 + 1)} + sage: [f.fmat(t,t,t,t,x,y) for x in f._FR.basis() for y in f._FR.basis()] [(-zeta60^14 + zeta60^6 + zeta60^4 - 1), - 1, - (-zeta60^14 + zeta60^6 + zeta60^4 - 1), - (zeta60^14 - zeta60^6 - zeta60^4 + 1)] - + 1, + (-zeta60^14 + zeta60^6 + zeta60^4 - 1), + (zeta60^14 - zeta60^6 - zeta60^4 + 1)] """ if self._FR.Nk_ij(a,b,x) == 0 or self._FR.Nk_ij(x,c,d) == 0 or self._FR.Nk_ij(b,c,y) == 0 or self._FR.Nk_ij(a,y,d) == 0: return 0 @@ -357,10 +360,11 @@ def fmatrix(self,a,b,c,d): EXAMPLES:: - sage: f=FMatrix(FusionRing("A1",2),fusion_label="c") - sage: f.get_solution(verbose=False); - equations: 14 - equations: 37 + sage: f=FMatrix(FusionRing("A1",2,fusion_labels="c",inject_variables=True)) + sage: f.fmatrix(c1,c1,c1,c1) + [fx0 fx1] + [fx2 fx3] + sage: f.find_cyclotomic_solution(verbose=False); adding equation... fx4 - 1 adding equation... fx10 - 1 sage: f.f_from(c1,c1,c1,c1) @@ -390,7 +394,7 @@ def findcases(self,output=False): EXAMPLES:: - sage: f=FMatrix(FusionRing("G2",1),fusion_label=["i0","t"]) + sage: f=FMatrix(FusionRing("G2",1,fusion_labels=("i0","t"))) sage: f.findcases() 5 sage: f.findcases(output=True) @@ -449,7 +453,8 @@ def f_from(self,a,b,c,d): EXAMPLES:: - sage: f=FMatrix(FusionRing("A1",3),fusion_label="a") + sage: fr = FusionRing("A1", 3, fusion_labels="a", inject_variables=True) + sage: f = FMatrix(fr) sage: f.fmatrix(a1,a1,a2,a2) [fx6 fx7] [fx8 fx9] @@ -472,15 +477,17 @@ def f_to(self,a,b,c,d): EXAMPLES:: - sage: B=FMatrix(FusionRing("B2",2),fusion_label="b") - sage: B.fmatrix(b2,b4,b4,b2) - [fx278 fx279 fx280] - [fx281 fx282 fx283] - [fx284 fx285 fx286] - sage: B.f_from(b2,b4,b4,b2) + sage: b22 = FusionRing("B2",2) + sage: b22.fusion_labels("b",inject_variables=True) + sage: B=FMatrix(b22) + sage: B.fmatrix(b2,b4,b2,b4) + [fx266 fx267 fx268] + [fx269 fx270 fx271] + [fx272 fx273 fx274] + sage: B.f_from(b2,b4,b2,b4) + [b1, b3, b5] + sage: B.f_to(b2,b4,b2,b4) [b1, b3, b5] - sage: B.f_to(b2,b4,b4,b2) - [b0, b1, b5] """ @@ -1123,7 +1130,7 @@ def update_equations(self): self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) self.ideal_basis.discard(0) - def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, prune=True, algorithm='', output=False): + def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, output=False): """ Solve the the hexagon and pentagon relations to evaluate the F-matrix. This method (omitting the orthogonality constraints) produces @@ -1136,9 +1143,6 @@ def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, pr - ``equations`` -- (optional) a set of equations to be solved. Defaults to the hexagon and pentagon equations. - - ``factor`` -- (default: ``True``). Set true to use - the sreduce method to simplify the hexagon and pentagon - equations before solving them. - ``algorithm`` -- (optional). Algorithm to compute Groebner Basis. - ``output`` -- (optional, default False). Output a dictionary of F-matrix values. This may be useful to see but may be omitted @@ -1147,36 +1151,35 @@ def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, pr EXAMPLES:: - sage: f = FMatrix(FusionRing("A2",1),fusion_label="a") - sage: f.get_solution(verbose=False,output=True) - equations: 8 - equations: 16 + sage: f=FMatrix(FusionRing("A2",1,fusion_labels="a",inject_variables=True),inject_variables=True) + creating variables fx1..fx8 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 + sage: f.find_cyclotomic_solution(output=True) + Setting up hexagons and pentagons... + Finding a Groebner basis... + Solving... + Fixing the gauge... adding equation... fx4 - 1 + Done! {(a2, a2, a2, a0, a1, a1): 1, - (a2, a2, a1, a2, a1, a0): 1, - (a2, a1, a2, a2, a0, a0): 1, - (a2, a1, a1, a1, a0, a2): 1, - (a1, a2, a2, a2, a0, a1): 1, - (a1, a2, a1, a1, a0, a0): 1, - (a1, a1, a2, a1, a2, a0): 1, - (a1, a1, a1, a0, a2, a2): 1} + (a2, a2, a1, a2, a1, a0): 1, + (a2, a1, a2, a2, a0, a0): 1, + (a2, a1, a1, a1, a0, a2): 1, + (a1, a2, a2, a2, a0, a1): 1, + (a1, a2, a1, a1, a0, a0): 1, + (a1, a1, a2, a1, a2, a0): 1, + (a1, a1, a1, a0, a2, a2): 1} After you successfully run ``get_solution`` you may check the correctness of the F-matrix by running :meth:`hexagon` and :meth:`pentagon`. These should return empty lists - of equations. In this example, we turn off the factor - and prune optimizations to test all instances. + of equations. EXAMPLES:: - sage: f.hexagon(factor=False) - equations: 0 - [] - sage: f.hexagon(factor=False,side="right") - equations: 0 + sage: f.get_defining_equations("hexagons") [] - sage: f.pentagon(factor=False,prune=False) - equations: 0 + sage: f.get_defining_equations("pentagons") [] """ @@ -1186,7 +1189,6 @@ def find_cyclotomic_solution(self, equations=None, factor=True, verbose=True, pr if verbose: print("Setting up hexagons and pentagons...") equations = self.get_defining_equations("hexagons")+self.get_defining_equations("pentagons") - #equations = self.hexagon(verbose=False, factor=factor)+self.pentagon(verbose=False, factor=factor, prune=prune) if verbose: print("Finding a Groebner basis...") self.ideal_basis = set(Ideal(equations).groebner_basis(algorithm=algorithm)) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 4247c1e3df5..5eaac549be6 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -36,6 +36,12 @@ class FusionRing(WeylCharacterRing): - ``conjugate`` -- (default ``False``) set ``True`` to obtain the complex conjugate ring - ``cyclotomic_order`` -- (default computed depending on ``ct`` and ``k``) + - ``fusion_labels`` -- (default None) either a tuple of strings to use as labels of the + basis of simple objects, or a string from which the labels will be + constructed + - ``inject_variables`` -- (default ``False``): use with ``fusion_labels``. + If ``inject_variables`` is ``True``, the fusion labels will be variables + that can be accessed from the command line The cyclotomic order is an integer `N` such that all computations will return elements of the cyclotomic field of `N`-th roots of unity. @@ -284,7 +290,7 @@ class FusionRing(WeylCharacterRing): True """ @staticmethod - def __classcall__(cls, ct, k, base_ring=ZZ, prefix=None, style="coroots", conjugate=False, cyclotomic_order=None): + def __classcall__(cls, ct, k, base_ring=ZZ, prefix=None, style="coroots", conjugate=False, cyclotomic_order=None, fusion_labels=None, inject_variables=False): """ Normalize input to ensure a unique representation. @@ -326,7 +332,9 @@ def __classcall__(cls, ct, k, base_ring=ZZ, prefix=None, style="coroots", conjug return super(FusionRing, cls).__classcall__(cls, ct, base_ring=base_ring, prefix=prefix, style=style, k=k, conjugate=conjugate, - cyclotomic_order=cyclotomic_order) + cyclotomic_order=cyclotomic_order, + fusion_labels=fusion_labels, + inject_variables=inject_variables) def _test_verlinde(self, **options): """ diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index 0a1e8c18fd8..c254e79a1e8 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -90,7 +90,7 @@ class WeylCharacterRing(CombinatorialFreeModule): https://doc.sagemath.org/html/en/thematic_tutorials/lie.html """ @staticmethod - def __classcall__(cls, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conjugate=False, cyclotomic_order=None): + def __classcall__(cls, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conjugate=False, cyclotomic_order=None, fusion_labels=None, inject_variables=False): """ TESTS:: @@ -106,9 +106,9 @@ def __classcall__(cls, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, c prefix = ct[0]+str(ct[1]) else: prefix = repr(ct) - return super(WeylCharacterRing, cls).__classcall__(cls, ct, base_ring=base_ring, prefix=prefix, style=style, k=k, conjugate=conjugate, cyclotomic_order=cyclotomic_order) + return super(WeylCharacterRing, cls).__classcall__(cls, ct, base_ring=base_ring, prefix=prefix, style=style, k=k, conjugate=conjugate, cyclotomic_order=cyclotomic_order, fusion_labels=fusion_labels, inject_variables=inject_variables) - def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conjugate=False, cyclotomic_order=None): + def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conjugate=False, cyclotomic_order=None, fusion_labels=None, inject_variables=False): """ EXAMPLES:: @@ -196,6 +196,9 @@ def next_level(wt): self._cyclotomic_order = self._fg * self._l else: self._cyclotomic_order = cyclotomic_order + self._fusion_labels = fusion_labels + if fusion_labels: + self.fusion_labels(labels=fusion_labels, inject_variables=inject_variables) @cached_method def ambient(self): From beac8e74714ec954bf78cf01a4d07607417e5ffc Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Mon, 8 Mar 2021 14:44:42 -0800 Subject: [PATCH 019/414] add braid method in fusion_ring.py --- src/sage/combinat/root_system/fusion_ring.py | 137 ++++++++++++++++++- 1 file changed, 133 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 5eaac549be6..817390bec01 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -40,7 +40,7 @@ class FusionRing(WeylCharacterRing): basis of simple objects, or a string from which the labels will be constructed - ``inject_variables`` -- (default ``False``): use with ``fusion_labels``. - If ``inject_variables`` is ``True``, the fusion labels will be variables + If ``inject_variables`` is ``True``, the fusion labels will be variables that can be accessed from the command line The cyclotomic order is an integer `N` such that all computations @@ -828,7 +828,7 @@ def r_matrix(self, i, j, k, base_coercion=True): def global_q_dimension(self, base_coercion=True): r""" - Return `\sum d_i^2`, where the sum is over all simple objects + Return `\sum d_i^2`, where the sum is over all simple objects and `d_i` is the quantum dimension. It is a positive real number. EXAMPLES:: @@ -911,7 +911,7 @@ def D_minus(self, base_coercion=True): if (not base_coercion) or (self._basecoer is None): return ret return self._basecoer(ret) - + def is_multiplicity_free(self): """ Return ``True`` if the fusion multiplicities @@ -1068,6 +1068,136 @@ def gens_satisfy_braid_gp_rels(self,sig): singular = any(s.is_singular() for s in sig) return braid_rels and far_comm and not singular + ################################### + ### Braid group representations ### + ################################### + + def get_trees(self,top_row,root): + """ + Recursively enumerate all the admissible trees with given top row and root. + Returns a list of tuples (l1,...,lk) such that + root -> lk # m[-1], lk -> l_{k-1} # m[-2], ..., l1 -> m[0] # m[1], + with top_row = m + """ + if len(top_row) == 2: + m1, m2 = top_row + return [[]] if self.Nk_ij(m1,m2,root) else [] + else: + m1, m2 = top_row[:2] + return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] + + def get_comp_basis(self,a,b,n_strands): + """ + Get the so-called computational basis for Hom(b, a^n). The basis is a list of + (n-2)-tuples (m_1,...,m_{n//2},l_1,...,l_{(n-3)//2}) such that + each m_i is a monomial in a^2 and l_{j+1} \in l_j # a, and l[-1] \in a # b + """ + comp_basis = list() + for top in product((a*a).monomials(),repeat=n_strands//2): + #If the n_strands is odd, we must extend the top row by a fusing anyon + top_row = list(top)+[a]*(n_strands%2) + comp_basis.extend(tuple([*top,*levels]) for levels in self.get_trees(top_row,b)) + return comp_basis + + @lazy_attribute + def fmats(self): + """ + Construct an FMatrix factory to solve the pentagon relations and + organize the resulting F-symbols. We only need this attribute to compute + braid group representations. + """ + return FMatrix.FMatrix(self) + + def emap(self,mapper,input_args,worker_pool=None): + """ + Apply the given mapper to each element of the given input iterable and + return the results (with no duplicates) in a list. This method applies the + mapper in parallel if a worker_pool is provided. + + # INPUT: + mapper is a string specifying the name of a function defined in the + fast_parallel_fmats_methods module. + + input_args should be a tuple holding arguments to be passed to mapper + + ##NOTES: + If worker_pool is not provided, function maps and reduces on a single process. + If worker_pool is provided, the function attempts to determine whether it should + use multiprocessing based on the length of the input iterable. If it can't determine + the length of the input iterable then it uses multiprocessing with the default chunksize of 1 + if chunksize is not explicitly provided. + """ + n_proc = worker_pool._processes if worker_pool is not None else 1 + input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] + no_mp = worker_pool is None + #Map phase. Casting Async Object blocks execution... Each process holds results + #in its copy of fmats.temp_eqns + input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) + if no_mp: + list(map(executor,input_iter)) + else: + list(worker_pool.imap_unordered(executor,input_iter,chunksize=1)) + #Reduce phase + if no_mp: + results = collect_eqns(0) + else: + results = set() + for worker_results in worker_pool.imap_unordered(collect_eqns,range(worker_pool._processes),chunksize=1): + results.update(worker_results) + results = list(results) + return results + + def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands,use_mp=True): + """ + Compute generators of the Artin braid group on n_strands strands. If + fusing_anyon = a and total_topological_charge = b, the generators are + endomorphisms of Hom(a^n_strands, b) + """ + assert n_strands > 2, "The number of strands must be an integer greater than 2" + #Construct associated FMatrix object and solve for F-symbols + if not self.fmats.symbols_known: + self.fmats.find_real_orthogonal_solution() + + #Set multiprocessing parameters. Context can only be set once, so we try to set it + try: + set_start_method('fork') + except RuntimeError: + pass + pool = Pool() if use_mp else None + + #Set up computational basis and compute generators one at a time + a, b = fusing_anyon, total_topological_charge + comp_basis = self.get_comp_basis(a,b,n_strands) + d = len(comp_basis) + print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) + + #Compute diagonal odd-indexed generators using the 3j-symbols + gens = { 2*i+1 : diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2) } + + #Compute even-indexed generators using F-matrices + for k in range(1,n_strands//2): + entries = self.emap('sig_2k',(k,a,b,n_strands),pool) + gens[2*k] = matrix(dict(entries)) + + #If n_strands is odd, we compute the final generator + if n_strands % 2: + entries = self.emap('odd_one_out',(a,b,n_strands),pool) + gens[n_strands-1] = matrix(dict(entries)) + + return comp_basis, [gens[k] for k in sorted(gens)] + + def gens_satisfy_braid_gp_rels(self,sig): + """ + Determine if given iterable of n matrices defines a representation of + the Artin braid group on (n+1) strands. Tests correctness of + get_braid_generators method. + """ + n = len(sig) + braid_rels = all(sig[i] * sig[i+1] * sig[i] == sig[i+1] * sig[i] * sig[i+1] for i in range(n-1)) + far_comm = all(sig[i] * sig[j] == sig[j] * sig[i] for i, j in product(range(n),repeat=2) if abs(i-j) > 1 and i > j) + singular = any(s.is_singular() for s in sig) + return braid_rels and far_comm and not singular + class Element(WeylCharacterRing.Element): """ A class for FusionRing elements. @@ -1237,4 +1367,3 @@ def q_dimension(self, base_coercion=True): if (not base_coercion) or (self.parent()._basecoer is None): return ret return self.parent()._basecoer(ret) - From c635db99d9ef20ed1731225f7b663cc2a14a851a Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Mon, 8 Mar 2021 16:46:30 -0800 Subject: [PATCH 020/414] work on doctests --- src/sage/combinat/root_system/f_matrix.py | 26 +- .../fast_parallel_fmats_methods.pxd | 6 +- .../fast_parallel_fmats_methods.pyx | 224 +++++++++----- .../fast_parallel_fusion_ring_braid_repn.pyx | 86 +++++- src/sage/combinat/root_system/fusion_ring.py | 10 +- .../combinat/root_system/poly_tup_engine.pxd | 6 +- .../combinat/root_system/poly_tup_engine.pyx | 277 ++++++++++++++++-- 7 files changed, 500 insertions(+), 135 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 53d2ebefa1a..8fcbb999097 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -217,14 +217,18 @@ class FMatrix(): :meth:`find_orthogonal_solution` is much more powerful and is capable of handling large cases, sometimes - quickly but sometimes (in larger cases) hours of + quickly but sometimes (in larger cases) after hours of computation. Its F-matrices are not always in the cyclotomic field that is the base ring of the underlying FusionRing, but sometimes in an extension field adjoining some square roots. When this happens, the FusionRing is - modified, adding an attribute :attr:`_basecoer that is + modified, adding an attribute :attr:`_basecoer` that is a coercion from the cyclotomic field to the F-matrix. + EXAMPLES:: + + (B3 level 2, F4 level 1 conjugated) + """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): self._FR = fusion_ring @@ -252,7 +256,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab r = self._field.defining_polynomial().roots(ring=QQbar,multiplicities=False)[0] self._qqbar_embedding = self._field.hom([r],QQbar) self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } - self._ks = self.get_known_sq() + self._ks = dict() self._nnz = self.get_known_nonz() self._known_vals = dict() self.symbols_known = False @@ -522,7 +526,12 @@ def get_known_sq(self,eqns=None): """ if eqns is None: eqns = self.ideal_basis - return { variables(eq_tup)[0] : -eq_tup[-1][1] for eq_tup in eqns if tup_fixes_sq(eq_tup) } + ks = deepcopy(self._ks) + for eq_tup in eqns: + if tup_fixes_sq(eq_tup): + ks[variables(eq_tup)[0]] = -eq_tup[-1][1] + # return { variables(eq_tup)[0] : -eq_tup[-1][1] for eq_tup in eqns if tup_fixes_sq(eq_tup) } + return ks def get_known_nonz(self): """ @@ -861,6 +870,14 @@ def partition_eqns(self,graph,eqns=None,verbose=True): print(graph.connected_components_sizes()) return partition + def add_square_fixers(self): + """ + Add square fixing equations back to ideal basis + """ + for fx, rhs in self._ks.items(): + if fx not in self.solved: + self.ideal_basis.append(poly_to_tup(self._poly_ring.gen(fx)**2 - rhs)) + def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): """ Compute a Groebner basis for a set of equations partitioned according to their corresponding graph @@ -924,6 +941,7 @@ def get_explicit_solution(self,eqns=None,verbose=True): """ if eqns is None: eqns = self.ideal_basis + self.add_square_fixers() eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) F = self._field diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index 487b7b60e83..9eebf2e5c58 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -3,6 +3,8 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=*) cpdef update_reduce(factory, tuple eq_tup) cpdef compute_gb(factory, tuple args) cpdef update_child_fmats(factory, tuple data_tup) -cpdef pent_verify(factory, tuple mp_params) +# cpdef pent_verify(factory, tuple mp_params) -cdef _fmat(factory, a, b, c, d, x, y) +cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) + +# cpdef update_reduce_chunk(factory, args) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 5f2bc0c3f18..3d0084c50ab 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -20,7 +20,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.qqbar import number_field_elements_from_algebraics #Define a global temporary worker results repository -worker_results = list() +cdef list worker_results = list() ############################## ### Parallel code executor ### @@ -34,7 +34,23 @@ def executor(params): ## NOTE: When the parent process is forked, each worker gets a copy of every global variable. The virtual memory address of object X in the parent process equals the VIRTUAL memory address of the copy of object X in each - worker, so we may construct references to forked copies of X + worker, so we may construct references to forked copies of X using an id + obtained in the parent process + + TESTS: + + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor + sage: fmats = FMatrix(FusionRing("A1",3)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) + sage: executor(params) + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns + sage: len(collect_eqns(0)) == 63 + True + sage: fmats = FMatrix(FusionRing("E8",2)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) + sage: executor(params) + sage: len(collect_eqns(0)) == 11 + True """ (fn_name, fmats_id), args = params #Construct a reference to global FMatrix object in this worker's memory @@ -43,45 +59,71 @@ def executor(params): #Bind module method to FMatrix object in worker process, and call the method return getattr(mod,fn_name)(fmats_obj,args) + +###################################### +### Fast fusion coefficients cache ### +###################################### + +# from sage.misc.cachefunc import cached_function +# cdef dict _Nk_ij = dict() + +# cpdef _Nk_ij(factory,proc): +# cdef int coeff +# for a,b,c in product(factory._FR.basis(),repeat=3): +# try: +# coeff = (a*b).monomial_coefficients(copy=False)[c.weight()] +# except: +# coeff = 0 +# _Nk_ij[a,b,c] = coeff + +# cpdef int _Nk_ij(a,b,c): +# try: +# return (a*b).monomial_coefficients(copy=False)[c.weight()] +# except KeyError: +# return 0 +# +# _Nk_ij = cached_function(_Nk_ij, name='_Nk_ij') + + ############### ### Mappers ### ############### -cdef _fmat(factory, a, b, c, d, x, y): +cdef _fmat(fvars, _Nk_ij, one, a, b, c, d, x, y): """ Cython version of fmat class method. Using cdef for fastest dispatch """ - if factory._FR.Nk_ij(a,b,x) == 0 or factory._FR.Nk_ij(x,c,d) == 0 or factory._FR.Nk_ij(b,c,y) == 0 or factory._FR.Nk_ij(a,y,d) == 0: + if _Nk_ij(a,b,x) == 0 or _Nk_ij(x,c,d) == 0 or _Nk_ij(b,c,y) == 0 or _Nk_ij(a,y,d) == 0: return 0 - #Some known zero F-symbols - if a == factory._FR.one(): + #Some known F-symbols + if a == one: if x == b and y == d: return 1 else: return 0 - if b == factory._FR.one(): + if b == one: if x == a and y == c: return 1 else: return 0 - if c == factory._FR.one(): + if c == one: if x == d and y == b: return 1 else: return 0 - return factory._fvars[a,b,c,d,x,y] + return fvars[a,b,c,d,x,y] -cdef req_cy(factory, tuple sextuple, side="left"): +cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, one, tuple sextuple): """ Given an FMatrix factory and a sextuple, return a hexagon equation as a polynomial object """ a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = factory._FR.r_matrix(a,c,e,base_coercion=False)*_fmat(factory,a,c,b,d,e,g)*factory._FR.r_matrix(b,c,g,base_coercion=False) + lhs = r_matrix(a,c,e)*_fmat(fvars,Nk_ij,one,a,c,b,d,e,g)*r_matrix(b,c,g) rhs = 0 - for f in factory._FR.basis(): - rhs += _fmat(factory,c,a,b,d,e,f)*factory._FR.r_matrix(f,c,d,base_coercion=False)*_fmat(factory,a,b,c,d,f,g) + for f in basis: + rhs += _fmat(fvars,Nk_ij,one,c,a,b,d,e,f)*r_matrix(f,c,d)*_fmat(fvars,Nk_ij,one,a,b,c,d,f,g) return lhs-rhs @cython.wraparound(False) @@ -91,28 +133,40 @@ cpdef get_reduced_hexagons(factory, tuple mp_params): """ Set up and reduce the hexagon equations corresponding to this worker """ + #Set up multiprocessing parameters global worker_results cdef child_id, n_proc cdef unsigned long i child_id, n_proc = mp_params cdef tuple sextuple - for i, sextuple in enumerate(product(factory._FR.basis(),repeat=6)): + + #Pre-compute common parameters for speed + cdef tuple basis = tuple(factory._FR.basis()) + cdef dict fvars = factory._fvars + r_matrix = factory._FR.r_matrix + _Nk_ij = factory._FR.Nk_ij + one = factory._FR.one() + cdef ETuple _nnz = factory._nnz + cdef dict _ks = factory._ks + + #Computation loop + for i, sextuple in enumerate(product(basis,repeat=6)): if i % n_proc == child_id: - he = req_cy(factory,sextuple) + he = req_cy(basis,r_matrix,fvars,_Nk_ij,one,sextuple) if he: - worker_results.append(reduce_poly_dict(he.dict(),factory._nnz,factory._ks)) + worker_results.append(reduce_poly_dict(he.dict(),_nnz,_ks)) -cdef MPolynomial_libsingular feq_cy(factory, tuple nonuple, bint prune=False): +cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, one, zero, tuple nonuple, bint prune=False): """ Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object """ a, b, c, d, e, f, g, k, l = nonuple - cdef lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) + cdef lhs = _fmat(fvars,Nk_ij,one,f,c,d,e,g,l)*_fmat(fvars,Nk_ij,one,a,b,l,e,f,k) if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information - return factory._poly_ring.zero() - cdef rhs = factory._poly_ring.zero() - for h in factory._FR.basis(): - rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) + return zero + cdef rhs = zero + for h in basis: + rhs += _fmat(fvars,Nk_ij,one,a,b,c,g,f,h)*_fmat(fvars,Nk_ij,one,a,h,d,e,g,k)*_fmat(fvars,Nk_ij,one,b,c,d,k,h,l) return lhs - rhs @cython.wraparound(False) @@ -122,18 +176,31 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): """ Set up and reduce the pentagon equations corresponding to this worker """ + #Set up multiprocessing parameters global worker_results cdef int child_id, n_proc child_id, n_proc = mp_params cdef unsigned long i - cdef tuple nonuple + cdef tuple nonuple, red cdef MPolynomial_libsingular pe - for i, nonuple in enumerate(product(factory._FR.basis(),repeat=9)): + + #Pre-compute common parameters for speed + cdef tuple basis = tuple(factory._FR.basis()) + cdef dict fvars = factory._fvars + r_matrix = factory._FR.r_matrix + _Nk_ij = factory._FR.Nk_ij + one = factory._FR.one() + cdef ETuple _nnz = factory._nnz + cdef dict _ks = factory._ks + cdef MPolynomial_libsingular zero = factory._poly_ring.zero() + + #Computation loop + for i, nonuple in enumerate(product(basis,repeat=9)): if i % n_proc == child_id: - pe = feq_cy(factory,nonuple,prune=prune) + pe = feq_cy(basis,fvars,_Nk_ij,one,zero,nonuple,prune=prune) if pe: - worker_results.append(reduce_poly_dict(pe.dict(),factory._nnz,factory._ks)) - + red = reduce_poly_dict(pe.dict(),_nnz,_ks) + worker_results.append(red) cpdef update_reduce(factory, tuple eq_tup): """ @@ -141,12 +208,8 @@ cpdef update_reduce(factory, tuple eq_tup): """ global worker_results cdef dict eq_dict = subs(eq_tup,factory._kp) - cdef reduced - if tup_fixes_sq(tuple(eq_dict.items())): - reduced = to_monic(eq_dict) - else: - reduced = reduce_poly_dict(eq_dict,factory._nnz,factory._ks) - worker_results.append(reduced) + cdef tuple red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks) + worker_results.append(red) cpdef compute_gb(factory, tuple args): """ @@ -163,9 +226,7 @@ cpdef compute_gb(factory, tuple args): R = PolynomialRing(factory._FR.field(),len(sorted_vars),'a',order=term_order) #Zip tuples into R and compute Groebner basis - cdef idx_map = dict() - for new, old in enumerate(sorted_vars): - idx_map[old] = new + cdef idx_map = { old : new for new, old in enumerate(sorted_vars) } nvars = len(sorted_vars) cdef list polys = list() for eq_tup in eqns: @@ -173,23 +234,27 @@ cpdef compute_gb(factory, tuple args): gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") #Change back to fmats poly ring and append to temp_eqns - cdef dict inv_idx_map = dict() - for k, v in idx_map.items(): - inv_idx_map[v] = k + cdef dict inv_idx_map = { v : k for k, v in idx_map.items() } + cdef tuple t nvars = factory._poly_ring.ngens() for p in gb: - worker_results.append(resize(poly_to_tup(p),inv_idx_map,nvars)) + t = resize(poly_to_tup(p),inv_idx_map,nvars) + worker_results.append(t) -#One-to-all communication used to update fvars after triangular elim step. cpdef update_child_fmats(factory, tuple data_tup): - #fmats is assumed to be global before forking used to create the Pool object, + """ + One-to-all communication used to update fvars after triangular elim step. + """ + #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object factory._fvars, factory.solved, factory._ks, factory._var_degs = data_tup factory._nnz = factory.get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory.get_known_vals()) def get_appropriate_number_field(factory,non_cyclotomic_roots): - #If needed, find a NumberField containing all the roots + """ + If needed, find a NumberField containing the roots given roots + """ roots = [factory._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] return number_field_elements_from_algebraics(roots,minimal=True) @@ -208,40 +273,39 @@ def collect_eqns(proc): worker_results = list() return list(reduced) -#################### -### Verification ### -#################### - -cdef feq_verif(factory, tuple nonuple, float tol=5e-8): - """ - Check the pentagon equation corresponding to the given nonuple - """ - global worker_results - a, b, c, d, e, f, g, k, l = nonuple - cdef float diff, lhs, rhs - lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) - rhs = 0.0 - for h in factory._FR.basis(): - rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) - diff = lhs - rhs - if diff > tol or diff < -tol: - worker_results.append(diff) - - -@cython.wraparound(False) -@cython.nonecheck(False) -@cython.cdivision(True) -cpdef pent_verify(factory, tuple mp_params): - """ - Generate all the pentagon equations assigned to this process, and reduce them - """ - child_id, n_proc = mp_params - cdef float t0 - cdef tuple nonuple - cdef unsigned long i - for i, nonuple in enumerate(product(factory.FR.basis(),repeat=9)): - if i % n_proc == child_id: - feq_verif(factory,nonuple) - if i % 50000000 == 0 and i: - print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) - print("Ran through {} pentagons... Worker {} with pid {} reporting {} potential misses...".format(i,child_id,getpid(),len(worker_results))) +# #################### +# ### Verification ### +# #################### +# +# cdef feq_verif(factory, tuple nonuple, float tol=5e-8): +# """ +# Check the pentagon equation corresponding to the given nonuple +# """ +# global worker_results +# a, b, c, d, e, f, g, k, l = nonuple +# cdef float diff, lhs, rhs +# lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) +# rhs = 0.0 +# for h in factory._FR.basis(): +# rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) +# diff = lhs - rhs +# if diff > tol or diff < -tol: +# worker_results.append(diff) +# +# @cython.wraparound(False) +# @cython.nonecheck(False) +# @cython.cdivision(True) +# cpdef pent_verify(factory, tuple mp_params): +# """ +# Generate all the pentagon equations assigned to this process, and reduce them +# """ +# child_id, n_proc = mp_params +# cdef float t0 +# cdef tuple nonuple +# cdef unsigned long i +# for i, nonuple in enumerate(product(factory._FR.basis(),repeat=9)): +# if i % n_proc == child_id: +# feq_verif(factory,nonuple) +# if i % 50000000 == 0 and i: +# print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) +# print("Ran through {} pentagons... Worker {} with pid {} reporting {} potential misses...".format(i,child_id,getpid(),len(worker_results))) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 9ecc49bc084..5fcba25b9b9 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -31,6 +31,24 @@ def executor(params): every global variable. The virtual memory address of object X in the parent process equals the VIRTUAL memory address of the copy of object X in each worker, so we may construct references to forked copies of X + + TESTS: + + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor + sage: FR = FusionRing("A1",4) + sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) + sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) + sage: executor(params) + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results + sage: len(collect_results(0)) == 13 + True + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor, collect_results + sage: FR = FusionRing("B2",2) + sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) + sage: params = (('odd_one_out',id(FR)),(0,1,(X,Xp,5))) + sage: executor(params) + sage: len(collect_results(0)) == 54 + True """ (fn_name, fr_id), args = params #Construct a reference to global FMatrix object in this worker's memory @@ -48,7 +66,24 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two strands in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j of trees b -> xj # yj -> (a # a) # (a # a) + + EXAMPLES:: + + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import mid_sig_ij + sage: FR = FusionRing("A1",4) + sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) + sage: one.weight() + (1/2, -1/2) + sage: FR.get_comp_basis(one,two,4) + [(two, two), (two, idd), (idd, two)] + sage: mid_sig_ij(FR, (two, two), (two, idd), one, two) + (zeta48^10 - zeta48^2)*fx0*fx1*fx8 + (zeta48^2)*fx2*fx3*fx8 """ + #Pre-compute common parameters for efficiency + _fvars = fusion_ring.fmats._fvars + _Nk_ij = fusion_ring.Nk_ij + one = fusion_ring.one() + xi, yi = row xj, yj = col entry = 0 @@ -56,10 +91,10 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): for c in fusion_ring.basis(): for d in fusion_ring.basis(): ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) - f1 = _fmat(fusion_ring.fmats,a,a,yi,b,xi,c) - f2 = _fmat(fusion_ring.fmats,a,a,a,c,d,yi) - f3 = _fmat(fusion_ring.fmats,a,a,a,c,d,yj) - f4 = _fmat(fusion_ring.fmats,a,a,yj,b,xj,c) + f1 = _fmat(_fvars,_Nk_ij,one,a,a,yi,b,xi,c) + f2 = _fmat(_fvars,_Nk_ij,one,a,a,a,c,d,yi) + f3 = _fmat(_fvars,_Nk_ij,one,a,a,a,c,d,yj) + f4 = _fmat(_fvars,_Nk_ij,one,a,a,yj,b,xj,c) r = fusion_ring.r_matrix(a,a,d) if not phi.is_identity(): r = phi(r) @@ -71,13 +106,30 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): Compute the xi, xj entry of the braid generator on the right-most strands, corresponding to the tree b -> (xi # a) -> (a # a) # a, which results in a sum over j of trees b -> xj -> (a # a) # (a # a) + + EXAMPLES:: + + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import odd_one_out_ij + sage: FR = FusionRing("B2",2) + sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) + sage: X.weight() + (1/2, 1/2) + sage: FR.get_comp_basis(X,X,3) + [(Y2,), (Y1,), (I0,)] + sage: odd_one_out_ij(FR,Y2,Y1,X,X) + (zeta40^10)*fx205*fx208 + (zeta40^14 - zeta40^10 + zeta40^6 - zeta40^2)*fx206*fx209 + (zeta40^2)*fx207*fx210 """ - entry = 0 + #Pre-compute common parameters for efficiency + _fvars = fusion_ring.fmats._fvars + _Nk_ij = fusion_ring.Nk_ij + one = fusion_ring.one() + phi = fusion_ring.fmats.get_coerce_map_from_fr_cyclotomic_field() + entry = 0 for c in fusion_ring.basis(): ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) - f1 = _fmat(fusion_ring.fmats,a,a,a,b,xi,c) - f2 = _fmat(fusion_ring.fmats,a,a,a,b,xj,c) + f1 = _fmat(_fvars,_Nk_ij,one,a,a,a,b,xi,c) + f2 = _fmat(_fvars,_Nk_ij,one,a,a,a,b,xj,c) r = fusion_ring.r_matrix(a,a,c) if not phi.is_identity(): r = phi(r) @@ -95,6 +147,11 @@ cpdef sig_2k(fusion_ring, tuple args): """ Compute entries of the 2k-th braid generator """ + #Pre-compute common parameters for efficiency + _fvars = fusion_ring.fmats._fvars + _Nk_ij = fusion_ring.Nk_ij + one = fusion_ring.one() + child_id, n_proc, fn_args = args k, a, b, n_strands = fn_args cdef int ctr = -1 @@ -139,8 +196,8 @@ cpdef sig_2k(fusion_ring, tuple args): entry = 0 for p in fusion_ring.basis(): - f1 = _fmat(fusion_ring.fmats,top_left,m[k-1],m[k],root,l[k-2],p) - f2 = _fmat(fusion_ring.fmats,top_left,f,e,root,q,p) + f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[k-1],m[k],root,l[k-2],p) + f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,e,root,q,p) entry += f1 * mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 worker_results.append(((basis_dict[nnz_pos],i), entry)) entries.add((basis_dict[nnz_pos],i)) @@ -153,6 +210,11 @@ cpdef odd_one_out(fusion_ring, tuple args): Compute entries of the rightmost braid generator, in case we have an odd number of strands """ + #Pre-compute common parameters for efficiency + _fvars = fusion_ring.fmats._fvars + _Nk_ij = fusion_ring.Nk_ij + one = fusion_ring.one() + global worker_results child_id, n_proc, fn_args = args a, b, n_strands = fn_args @@ -192,8 +254,8 @@ cpdef odd_one_out(fusion_ring, tuple args): #Compute relevant entry entry = 0 for p in fusion_ring.basis(): - f1 = _fmat(fusion_ring.fmats,top_left,m[-1],a,root,l[-1],p) - f2 = _fmat(fusion_ring.fmats,top_left,f,a,root,q,p) + f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[-1],a,root,l[-1],p) + f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,a,root,q,p) entry += f1 * odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 worker_results.append(((basis_dict[nnz_pos],i), entry)) @@ -201,7 +263,7 @@ cpdef odd_one_out(fusion_ring, tuple args): ### Reducers ### ################ -def collect_eqns(proc): +def collect_results(proc): """ Helper function for returning processed results back to parent process. Trivial reducer: simply collects objects with the same key in the worker diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 817390bec01..1fa7dea7906 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -15,7 +15,7 @@ from multiprocessing import Pool, set_start_method from sage.combinat.q_analogues import q_int import sage.combinat.root_system.f_matrix as FMatrix -from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_eqns, executor +from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results, executor from sage.combinat.root_system.weyl_characters import WeylCharacterRing from sage.matrix.constructor import matrix from sage.matrix.special import diagonal_matrix @@ -1008,10 +1008,10 @@ def emap(self,mapper,input_args,worker_pool=None): list(worker_pool.imap_unordered(executor,input_iter,chunksize=1)) #Reduce phase if no_mp: - results = collect_eqns(0) + results = collect_results(0) else: results = set() - for worker_results in worker_pool.imap_unordered(collect_eqns,range(worker_pool._processes),chunksize=1): + for worker_results in worker_pool.imap_unordered(collect_results,range(worker_pool._processes),chunksize=1): results.update(worker_results) results = list(results) return results @@ -1139,10 +1139,10 @@ def emap(self,mapper,input_args,worker_pool=None): list(worker_pool.imap_unordered(executor,input_iter,chunksize=1)) #Reduce phase if no_mp: - results = collect_eqns(0) + results = collect_results(0) else: results = set() - for worker_results in worker_pool.imap_unordered(collect_eqns,range(worker_pool._processes),chunksize=1): + for worker_results in worker_pool.imap_unordered(collect_results,range(worker_pool._processes),chunksize=1): results.update(worker_results) results = list(results) return results diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 78d4dffa4e5..5d5e49263d3 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -5,17 +5,17 @@ from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libs cpdef tuple poly_to_tup(MPolynomial_libsingular poly) cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) -cdef ETuple degrees(tuple poly_tup) cpdef ETuple get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cpdef bint tup_fixes_sq(tuple eq_tup) cpdef dict subs_squares(dict eq_dict, dict known_sq) -cdef dict remove_gcf(dict eq_dict, ETuple nonz) cdef tuple to_monic(dict eq_dict) cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq) cpdef dict compute_known_powers(ETuple max_deg, dict val_dict) cpdef dict subs(tuple poly_tup, dict known_powers) -cdef tuple tup_mul(tuple p1, tuple p2) cpdef int poly_tup_cmp(tuple tleft, tuple tright) + +cdef tuple tup_mul(tuple p1, tuple p2) +cdef dict remove_gcf(dict eq_dict, ETuple nonz) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index f74d674619b..9a3b0b7d83d 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -1,6 +1,6 @@ -################################################### -### Arithmetic Engine for polynomials as tuples ### -################################################### +""" +Arithmetic Engine for polynomials as tuples +""" # **************************************************************************** # Copyright (C) 2021 Guillermo Aboumrad # @@ -12,7 +12,11 @@ from functools import cmp_to_key from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.term_order import TermOrder -from sage.rings.rational_field import RationalField as QQ +from sage.rings.rational_field import QQ + +#Pre-compute common values for speed +one = QQ.one() +degrevlex_sortkey = TermOrder().sortkey_degrevlex ########### ### API ### @@ -22,6 +26,15 @@ cpdef tuple poly_to_tup(MPolynomial_libsingular poly): """ Convert a polynomial object into the internal representation as tuple of (ETuple exp, NumberFieldElement coeff) pairs + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: R. = PolynomialRing(QQ) + sage: poly_to_tup(x**2 + 1) + (((2, 0), 1), ((0, 0), 1)) + sage: poly_to_tup(x**2*y**4 - 4/5*x*y**2 + 1/3 * y) + (((2, 4), 1), ((1, 2), -4/5), ((0, 1), 1/3)) """ return tuple(poly.dict().items()) @@ -29,7 +42,33 @@ cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingu """ Return a polynomial object from its tuple representation. Inverse of poly_to_tup. poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup && tup_to_poly(poly_to_tup(eq), eq.parent()) == eq - Assumes parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup + + NOTE: + Assumes parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import tup_to_poly + sage: R. = PolynomialRing(CyclotomicField(20)) + sage: poly_tup = (((2,0),1), ((0,0),1)) + sage: tup_to_poly(poly_tup, parent=R) + x^2 + 1 + sage: poly = x**2*y**4 - 4/5*x*y**2 + 1/3 * y + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: tup_to_poly(poly_to_tup(poly), parent=R) == poly + True + + TESTS: + + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, tup_to_poly + sage: R. = PolynomialRing(CyclotomicField(20)) + sage: r = R.random_element() + sage: tup_to_poly(poly_to_tup(r), parent=R) == r + True + sage: R = PolynomialRing(QQ, 'fx', 100) + sage: r = R.random_element() + sage: tup_to_poly(poly_to_tup(r), parent=R) == r + True """ # Maybe the following is faster but we need to ensure all coefficients are # already in fmats._poly_ring.base_ring() so that implicit casting is avoided @@ -44,7 +83,22 @@ cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingu def tup_to_univ_poly(tuple eq_tup, gen): """ Given a tuple of pairs representing a univariate polynomial and a univariate - polynomial ring generator, return a univariate polynomial object + polynomial ring generator, return a univariate polynomial object. + + Each pair in the tuple is assumed to be of the form (ETuple, coeff), where + coeff is an element of gen.parent().base_ring(). + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import tup_to_univ_poly + sage: from sage.rings.polynomial.polydict import ETuple + sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) + sage: b = QQ['b'].gen() + sage: tup_to_univ_poly(poly_tup, b) + 2*b^3 - b - 2/3 + sage: poly_tup = ((ETuple([0, 0, 0]), -1/5),) + sage: tup_to_univ_poly(poly_tup, b) + -1/5 """ univ_tup = tuple((exp.nonzero_values()[0] if exp.nonzero_values() else 0, c) for exp, c in eq_tup) return sum(c * gen ** p for p, c in univ_tup) @@ -55,6 +109,24 @@ cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars): This method is used for creating polynomial objects with the "right number" of variables for computing Groebner bases of the partitioned equations graph and for adding constraints ensuring certain F-symbols are nonzero + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import resize + sage: from sage.rings.polynomial.polydict import ETuple + sage: poly_tup = ((ETuple([0,3,0,2]),2), (ETuple([0,1,0,1]),-1), (ETuple([0,0,0,0]),-2/3)) + sage: idx_map = { 1 : 0, 3 : 1 } + sage: resize(poly_tup,idx_map,2) + (((3, 2), 2), ((1, 1), -1), ((0, 0), -2/3)) + + sage: R = PolynomialRing(ZZ, 'fx', 20) + sage: R.inject_variables() + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19 + sage: sparse_poly = fx0**2 * fx17 + fx3 + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, tup_to_poly + sage: S. = PolynomialRing(ZZ) + sage: tup_to_poly(resize(poly_to_tup(sparse_poly),{0:0,3:1,17:2},3), parent=S) + x^2*z + y """ cdef ETuple new_e cdef list resized = list() @@ -82,7 +154,17 @@ cdef ETuple degrees(tuple poly_tup): cpdef ETuple get_variables_degrees(list eqns): """ Find maximum degrees for each variable in equations - """ + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees + sage: R. = PolynomialRing(QQ) + sage: polys = [x**2 + 1, x*y*z**2 - 4*x*y, x*z**3 - 4/3*y + 1] + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: get_variables_degrees([poly_to_tup(p) for p in polys]) + (2, 1, 3) + """ + if not eqns: return ETuple([]) cdef tuple eq_tup cdef ETuple max_deg max_deg = degrees(eqns[0]) @@ -93,12 +175,38 @@ cpdef ETuple get_variables_degrees(list eqns): cpdef list variables(tuple eq_tup): """ Return indices of all variables appearing in eq_tup + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import variables + sage: from sage.rings.polynomial.polydict import ETuple + sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) + sage: variables(poly_tup) + [1] + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: R. = PolynomialRing(QQ) + sage: variables(poly_to_tup(x*2*y + y**3 - 4/3*x)) + [0, 1] + sage: variables(poly_to_tup(R(1/4))) + [] """ return degrees(eq_tup).nonzero_positions() cpdef constant_coeff(tuple eq_tup): """ Return the constant coefficient of the polynomial represented by given tuple + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import constant_coeff + sage: from sage.rings.polynomial.polydict import ETuple + sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) + sage: constant_coeff(poly_tup) + -2/3 + sage: R. = PolynomialRing(QQ) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: constant_coeff(poly_to_tup(x**5 + x*y*z - 9)) + -9 """ for exp, coeff in eq_tup: if exp.is_constant(): @@ -108,6 +216,15 @@ cpdef constant_coeff(tuple eq_tup): cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): """ Apply `coeff_map` to coefficients + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import apply_coeff_map + sage: sq = lambda x : x**2 + sage: R. = PolynomialRing(QQ) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, tup_to_poly + sage: tup_to_poly(apply_coeff_map(poly_to_tup(x + 2*y + 3*z), sq), parent=R) + x + 4*y + 9*z """ cdef list new_tup = list() for exp, coeff in eq_tup: @@ -118,7 +235,23 @@ cpdef bint tup_fixes_sq(tuple eq_tup): """ Determine if given equation fixes the square of a variable. An equation fixes the sq of a variable if it is of the form `a*x^2 + c` for *nonzero* constants `a`, `c` - """ + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import tup_fixes_sq + sage: R. = PolynomialRing(QQ) + sage: sq_fixer = 3*z**2 - 1/8 + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: tup_fixes_sq(poly_to_tup(sq_fixer)) + True + sage: tup_fixes_sq(poly_to_tup(y**3 + 2)) + False + sage: tup_fixes_sq(poly_to_tup(x*y + 2)) + False + sage: tup_fixes_sq(poly_to_tup(x**2 + y**2 + 2)) + False + """ + #Make this faster by combining two conditions into one... don't create temp variables return len(eq_tup) == 2 and len(variables(eq_tup)) == 1 and eq_tup[0][0].nonzero_values() == [2] ###################### @@ -130,6 +263,16 @@ cpdef dict subs_squares(dict eq_dict, dict known_sq): Substitutes for known squares in a given polynomial. The parameter known_sq is a dictionary of (int i, NumberFieldElement a) pairs such that x_i^2 - a == 0 Returns a dictionary of (ETuple, coeff) pairs representing polynomial + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import subs_squares + sage: R. = PolynomialRing(QQ) + sage: poly = x**2 + y**3 + x*z**3 + sage: known_sq = { 0 : 2, 1 : -1, 2 : -1/2 } + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: subs_squares(dict(poly_to_tup(poly)), known_sq) + {(0, 0, 0): 2, (0, 1, 0): -1, (1, 0, 1): -1/2} """ cdef dict subbed, new_e cdef ETuple exp, lm @@ -151,7 +294,7 @@ cpdef dict subs_squares(dict eq_dict, dict known_sq): subbed[exp] += coeff else: subbed[exp] = coeff - return { exp : a for exp, a in subbed.items() } + return subbed cdef dict remove_gcf(dict eq_dict, ETuple nonz): """ @@ -164,7 +307,10 @@ cdef dict remove_gcf(dict eq_dict, ETuple nonz): common_powers = nonz for exp, c in eq_dict.items(): common_powers = common_powers.emin(exp) - return { exp.esub(common_powers) : c for exp, c in eq_dict.items() } + cdef dict ret = dict() + for exp, c in eq_dict.items(): + ret[exp.esub(common_powers)] = c + return ret cdef tuple to_monic(dict eq_dict): """ @@ -173,14 +319,14 @@ cdef tuple to_monic(dict eq_dict): (default for multivariate polynomial rings) """ if not eq_dict: return tuple() - it = reversed(sorted(eq_dict, key=TermOrder().sortkey_degrevlex)) - lm = next(it) + cdef list ord_monoms = sorted(eq_dict, key=degrevlex_sortkey) + cdef ETuple lm = ord_monoms[-1] lc = eq_dict[lm] if not lc: return tuple() - cdef ETuple exp - cdef list ret = [(lm, QQ().one())] + cdef list ret = [(lm, one)] inv_lc = lc.inverse_of_unit() - for exp in it: + cdef ETuple exp + for exp in reversed(ord_monoms[:-1]): ret.append((exp, inv_lc * eq_dict[exp])) return tuple(ret) @@ -198,15 +344,42 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): """ - Pre-compute powers of known values for efficiency - """ + Pre-compute powers of known values for efficiency when preparing to substitute + into a list of polynomials. + + INPUTS: + + max_deg is an ETuple indicating the maximal degree of each variable + val_dict is a dictionary of (var_idx, poly_tup) key-value pairs. poly_tup + is a tuple of (ETuple, coeff) pairs reperesenting a multivariate polynomial + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import compute_known_powers + sage: R. = PolynomialRing(QQ) + sage: polys = [x**3 + 1, x**2*y + z**3, y**2 - 3*y] + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } + sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees + sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) + sage: compute_known_powers(max_deg, known_val) + {0: [(((0, 0, 0), 1),), + (((0, 0, 0), -1),), + (((0, 0, 0), 1),), + (((0, 0, 0), -1),)], + 2: [(((0, 0, 0), 1),), + (((0, 2, 0), 1),), + (((0, 4, 0), 1),), + (((0, 6, 0), 1),)]} + """ + if not max_deg: return dict() assert max(max_deg.nonzero_values(sort=False)) <= 100, "NotImplementedError: Cannot substitute for degree larger than 100" max_deg = max_deg.emin(ETuple({ idx : 100 for idx in val_dict }, len(max_deg))) cdef dict known_powers #Get polynomial unit as tuple to initialize list elements - cdef tuple one = tuple(((ETuple({},len(max_deg)),QQ().one()),)) - cdef int d, var_idx - known_powers = { var_idx : [one]*(d+1) for var_idx, d in max_deg.sparse_iter() } + cdef tuple one_tup = ((max_deg._new(), one),) + cdef int d, power, var_idx + known_powers = { var_idx : [one_tup]*(d+1) for var_idx, d in max_deg.sparse_iter() } for var_idx, d in max_deg.sparse_iter(): for power in range(d): known_powers[var_idx][power+1] = tup_mul(known_powers[var_idx][power],val_dict[var_idx]) @@ -215,23 +388,39 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): cpdef dict subs(tuple poly_tup, dict known_powers): """ Substitute given variables into a polynomial tuple + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import subs + sage: R. = PolynomialRing(QQ) + sage: polys = [x**3 + 1, x**2*y + z**3, y**2 - 3*y] + sage: from sage.combinat.root_system.poly_tup_engine import compute_known_powers, get_variables_degrees, poly_to_tup + sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } + sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) + sage: poly_tup = poly_to_tup(polys[0]) + sage: subs(poly_tup, compute_known_powers(max_deg, known_val)) + {(0, 0, 0): 0} + sage: poly_tup = poly_to_tup(polys[1]) + sage: subs(poly_tup, compute_known_powers(max_deg, known_val)) + {(0, 1, 0): 1, (0, 6, 0): 1} """ cdef dict subbed = {} - cdef ETuple exp, m + cdef ETuple exp, m, shifted_exp cdef int var_idx, power cdef tuple temp for exp, coeff in poly_tup: #Get polynomial unit as tuple - temp = tuple(((ETuple({},len(exp)),QQ().one()),)) + temp = ((exp._new(), one),) for var_idx, power in exp.sparse_iter(): if var_idx in known_powers: exp = exp.eadd_p(-power,var_idx) temp = tup_mul(temp,known_powers[var_idx][power]) for m, c in temp: - if exp.eadd(m) in subbed: - subbed[exp.eadd(m)] += coeff*c + shifted_exp = exp.eadd(m) + if shifted_exp in subbed: + subbed[shifted_exp] += coeff*c else: - subbed[exp.eadd(m)] = coeff*c + subbed[shifted_exp] = coeff*c return subbed cdef tuple tup_mul(tuple p1, tuple p2): @@ -239,13 +428,14 @@ cdef tuple tup_mul(tuple p1, tuple p2): Multiplication of two tuples... may have to make this faster """ cdef dict prod = dict() - cdef ETuple xi, yj + cdef ETuple xi, yj, shifted_exp for xi, ai in p1: for yj, bj in p2: - if xi.eadd(yj) in prod: - prod[xi.eadd(yj)] += ai*bj + shifted_exp = xi.eadd(yj) + if shifted_exp in prod: + prod[shifted_exp] += ai*bj else: - prod[xi.eadd(yj)] = ai*bj + prod[shifted_exp] = ai*bj return tuple(prod.items()) ############### @@ -257,6 +447,35 @@ cdef tuple tup_mul(tuple p1, tuple p2): cpdef int poly_tup_cmp(tuple tleft, tuple tright): """ Determine which polynomial is larger with respect to the degrevlex ordering + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp + sage: R. = PolynomialRing(QQ) + sage: p1 = x*y*z - x**2 + 3/2 + sage: p2 = x*y*z - x * y +1/2 + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) + True + sage: R. = PolynomialRing(CyclotomicField(20)) + sage: zeta20 = R.base_ring().gen() + sage: p1 = zeta20**2 * x*z**2 - 2*zeta20 + sage: p2 = y**3 + 1/4 + sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) + True + + TESTS: + + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp, poly_to_tup + sage: R. = PolynomialRing(CyclotomicField(20)) + sage: p1 = R.random_element() + sage: p2 = R.random_element() + sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) + True + sage: (p1 > p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) > 0) + True + sage: poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p1)) == 0 + True """ cdef int i, ret, sf, sg, val cdef ETuple f, g From 7d96da941752178061812e2da0b8d5c45a509b35 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 12 Mar 2021 16:56:14 -0800 Subject: [PATCH 021/414] doctests added, and braid group representations --- .../reference/combinat/media/fusiontree.png | Bin 0 -> 37425 bytes .../reference/combinat/media/fusiontree.tex | 29 +++ src/doc/en/reference/references/index.rst | 2 + src/sage/combinat/root_system/f_matrix.py | 15 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 12 +- src/sage/combinat/root_system/fusion_ring.py | 213 +++++++----------- .../combinat/root_system/poly_tup_engine.pyx | 35 ++- 7 files changed, 160 insertions(+), 146 deletions(-) create mode 100644 src/doc/en/reference/combinat/media/fusiontree.png create mode 100644 src/doc/en/reference/combinat/media/fusiontree.tex diff --git a/src/doc/en/reference/combinat/media/fusiontree.png b/src/doc/en/reference/combinat/media/fusiontree.png new file mode 100644 index 0000000000000000000000000000000000000000..1fde2a94c81f02f7bd8d9752d5b46c1469436d48 GIT binary patch literal 37425 zcmeGFc|6r!_dkxmfd$L?4vpR?$wVUbDUq? z&9Yn8dD*m{EsUqP_adKzz1m~@EWf{X)4Oor-@3j(n3D^gj=UJB;GVYrOjOs=M_2US zzDj*&m*CxljmAEVi#pxMKj+odPX4#|RU5T?2OS6;w{bL&*DG&K=9N9;ce|Oc53V)y z+<8cNsNvYwcc)c)nR~6QZrJbr)OClO#Um@f6BDKzX-u{)cU(GVT=AOX#B=4*ql8nJ zPTc7Ea`?EHX+wjY3@=|c@;?5hOQh}nUPE@<*v#nH_x0|l=c1ekS~qvqYfq|-l_o@- zSG72xwD{z-Ev+Aee%@Sho>SRvl{xbouF@aLU* zGgc;S5CzY0yfCM*((K3gs&7?AV=uM^G65=H=aoMB6hSP?&X%)hGXC`6yA!n|@ulmg zIdtV;Vko_L*xvtMg|?Rn19VuJV;934BT(ojIzpCQ8yXXv%6-td)qK^ z=+09HyrG|YgKQ2=U!8bK{(JPvw~v z8EdI32mb+j7$(D3!>=pbVzS+GJFzXB9r`M4L!cC6B>P=oTZyc(*zx~$`hP6=|4j>$ z<`x7cX*&3ie!u1I`tlFke^}>D{cXSe=9cwu0)D@Leq#Ig!w1aU8m?@ucK%@|n%IN; z=U{h3|6%LzqWHk3=kaISo73AZ*S)$lpvmpp+YLACsx}5q4yZrxV7*Uct3Ubk?1-OD}Nin~mwM&+>xWYOidmdUK`r$>g&?0)A{S?-A56 z!|&(a&d)an)*kiwa@g%mtV7_$fYwTf1!s?W&GV~$JpSy7l9&}6<~ILG4|?NY`}p~^ z&I6)#w|^TQ-;MFs<^BcUnD*)AWe4*cAOBKOKDzNvBQL>Z&M-BNk;_dcd_Cr^eEIgT zH~I0~iuVjvzWF_^62EPziA|qEE3ou=`HHQCHf%~`1j*m_W z8*-LjUUhu_#Lv-n%|-2j70K(b6&k$XTC%@9L$SxPS~oDM{K}6z&1Yg)`3-yT^W3_8 zsCnbuv`?WrrcGlz>uz;S`+8#gg@Fo>wlVU5js5mM4w?Gn%xTjST~ z2WRUZ)(vW@{wL=ey85)%zdEbfeS%}@*NNq)qn`bGyD|QRU&TLl**Y5^HMAMFkNHhK`{3t!)0KF|L-dty@M?;aCNd&X;V zTnGCD6N}sC$GC(~-C}WceS7Qg1-?ZAzRRz;KYg9&`)Z}HT0qK&-*+iSdG&KL$J-(;a(dSJk$#?q+gN5-vN z?f+whL*s*)c>!OK`*sg_w{hmb6n(ih(L8ogjM1`{o0l{_bZ8q>QVreAbZK{;^!&Zo z!;xck-@3-Ge0J&P_loBo8m<^u`=#ZA#6bcz^7)?nwR`J_^0V<~PrcI{zZ@+mrdQu6 z3jJ4|9&PguG%Fls7ufu^=uF=cOI2GxJWH?~>i6Bey)m!+(6Uw44lkK6Q@DREuQ9ZL zdvlRqQ-fLax2P>|)|}mQV*Bf1i{`b}nEY1=fp(+pPHZjSbMoT#=tYx$y>=NI*ygmc z7|Z0~mUo`B<2%-gZpN|kU%y7zM}AJLymYjFd(@phl`k(^`aK@=rlM0 zRY{rM&8BgTm?E0E@k!o@xB3syEjhos^~33n#X1{r9c-&RYyNm^3%b_+pP`39pRx9% zyHEHi+H;~gJgCJ52CphC=lHQs9YmAJ&w0ce<$<^PIltemy9^sxEN5`w;7%R7lyOXN z&_;t7;b=U1ZdMzJ2Kc7Fj%A_*yU0`!ndGw$2=PodBy|O?{#zf*E z103-`B0sPE;rA*($M?gwuX|4XhOifqIaoc#tZ3M?)UPLw1~xu)ICJ)d--Ds59ooB% z@&0fu&o@`{*z~Pxfd8fSFAcxnC^K7hV+&ez5-fdM-YrPLdbH;l7dy@e^`P0mt zWGo|ok#hRqv5fhh-rlo!*^)JTi7CG%2@NC1)D$~lm_nR`+h9yKFm{8m$-dLuHq*x?Y zvxMmCNY`r@3u^Ps9U3R*P5gSS?$Ox1M_XI+XY@E;`}I*u|bMQ@>eZmp>+Hr^h0(LQy|NIQw} zWz~THnR4sVS{3iW!Rf!QUVgmw8(fu6^R`R3Y93tiD3n~;^y%KouU3{ZMu+R&My0pZ zfDW=F%WrHtxj^%ORDW2)qgbQPdj6XxHI#gcr8G|w|rZTx96ITsz$%~pEl z>X(O&eG*sgrZH;OlsY#Dzw(5CEZ(g-d;H6vN}MBP%|!IKWi89>J!kp-*j_Zbz8>c2 z+2z3c^9KWe-BTH572o{s>$9}YmCs=JW1mbY39CyO6|ko4=(tBc0)GaRxO_?v&OQ?! zSa-^7$^R^ibQc@Zy34n>Bo(ecsp3$mX zTPs2wB*NT}l844RVC;zCyN>*=^)HrnCiVVU@aKX79?4l6LCHq2Bu_{IlcJ zzi0i~-q=!`_kS%E}3?$CIvhq0p;fONN}ujcKC+gg7Fon3$Zy=hfSo>}?M z-t*_o)(i0Q`+c)$>ND@DKQk29G}e4BKeHQic%!o{IseE;LfkR8N=JO@XR({T6BsExrr%xT2G<<4}bnq%wCNAsE)pgIi4m}nT z2+?mFExK?4_9g%u;rV$}YQr=YdEy+speX~eA1QZeZ;I7C7#wiMeNwZ&}{-BdEPnNkKI5PVMm@r=eOVBUfBAy4_T9epOGN zO7LAC{djTt)#3F2YRZbEx(Rqh>)H4~zvWlgE}7K)E#)?EK#H(D|KX_gV|SY0|2Px< zp=p1$>%*9B3q?lu-jBzXKNxY@YFL5~c3%mg?bWJ2Ox%}3w|Hyww(9Ud=WT4wZn%D@ ze&ncb5h0ewsltkT-?6Y4U)i7fC z_y1B-d6p2n>d3V>YtKhNo4if8kS`wb{A)ga#~yC=-A+C4zdf9EUDHTK)7T0&IYh}a z$H;EdHDPX~PkBP@{D6@dbxo#~?Yv>fH{I)csgiN*e)F5evguQgpb7O?J+$%@)W$ju zyIsHUSbS5#*;{;Onnzq$%V7!LPbLl>;8%8XRosEx5ZCFZs%1y4w3x+%b(BjTZ4JJ` ztsYCerYQEv9OLx6C@AO+_La9M4Dim4>m`#o9oXsTe40 z@JtUUjj@#oElgG2qd-xCrK}qSWOP|8TU{$ln?VouZ8y>e2UcnQ{p6e zF1*Kx`^g*5QC&yu)3-bWIt)ONAVuB)K}$72P;RKgW+G?{2(p$DbYThz8mgmQKm@%3 zLDXOgg7=(MVU6v&`}Zzf~KIc1RdzRS*%gWK{dr}{ocyVwg_3H=TR$L)fn0L zf%Kg^puv9kateu5Q~WZRzN29w|K8u&uA9D&@}t?9Sj`YwgC=HcB>O&#z8l~>8tnJd zd)>eYtDzXj6?j7r$|R->dZN@65!_Q0jRBp{!CBC$5v6bW3DZ#{M%&37?00)x@IeH& zMb9-=oii7sPdc1*!(Q?SxJyhpEX-tNJabMFc7+-?5$_*>lvV@Tlsvqg?n*5MvAMP=m^$sYxdy)Pie>UFJZe zDjz1+GUk82H_H2umwlH||8(>(lD)f&s-h#l)TOwCSG zhjmy=qKexmYk>EB(;=EKy(LZ&BooIzXbhE!Htq=s0=_^(ybuVe0mLp^m{*iq@GU72 zmw+ME0IRe4UQVZ@SFg^D8Rc*y#&~5#$PNvnZ3#ei<)EgUS!CAkDFE3pnQWcGx2;hjAb*^3bEIk0YtTg$N-2Y%MmR$w$lRZju0Xf zAfg5qs8&gm(8&;E#{fzG5LtsoGQ&7*A_i)uflM*bSouKq#&&U09$Ezpz-#wlS%Y|e z4g-M@n^8oOwX8w5FmbwZ*gAom>Ps0MifbR$QndZg=6kLTVbZ-6lj}ZzPy2z zy0CL9e>eteWCwztJ(aRjvRG*^vDxFH^@ttlpsc|TB+Q+z4?@O+BA%P9!A>p8UaL0- zI!sgRg*k4L>Gi_#u|`&y+G*a*~RVl(DJYXrG~&n4MK2)r)e=# zm?@yo;O#A$(c>Q}jtp5Y4c$TVhlz-Qvd=dNGq97`s1)*>=2P#(9$Eo%zF9}AoLVYz zsym48J;8)luy^0Zqae-I&WnqwMfC`UfR*v>t~Lra~A4H%+h@rN9(zNN-; zP*F~DGK*3>#gU_*%3lt0_TW1s%ruOZQR4$@E@DbVB_nZ)CMN+Qd4ZVo=(QjG&6q4} z&`K#47>CW5hA!*`{)*r}3`1nYl-|=Qiilvw=CU0~YovSF?}L5mdrFiV+kjdg z?48P=1ZoZtHD8A6SX`3LYVdetyE0g}iKI<&@v;VSp}(CZe^?>ki+n48lB_`jpBSk! zSVaQ5z%@sIG^WcZ@HsNXNE&)jkqE8MWg9F(hxJwLKqzgZm=}=oIGHVx9=6xAJE(R_ zg}z6DZ6nYiLMO)>31^u?3ShIl7^0^ZI$S|ticil4$T6Gy0PCOV!1v?KhanDpYL*&mz9 zS%b!(iS>UFKcX8W2t-3Z&dg81n;SZs9lr&5(IC9L!65oheof>rYp~yE8i)OjAH!&z zHeqHQbd?>yBr@c`-?gxyNTml!geTo4PVBDArh6fuwo$~N-KF=sOfwCOCQY0-L?`>L zToYlkck*u^Ija7bZNAen*PvrRAP$;dplz{sx4TvH=68vcebvsgYWvx6#wO$w9Zh??Rk zwSAT{CNyOOYi?-P5E?z+!`4bhaotIz+o=(e+%mLGp`GIdxW;`G82t@R2xuma$#+1N_FSNVO59#>{hc zpC+5>)?NB4w}cMP-)aK&=FQ>XA9=0b4X&e}QM@D1te$45WFtdhOli@FG zkU$>6Azsc6{mjy{P}U%N;^rEwMtay9(>Pj~tC@V9PxcZ)q}oGLZ>9td5JqarpKPfC zxlSaOiH8>^$#+Z2DN!oHYSPfnGibVVb3-@FcCY-nqm~-4MIN@}SvsR-4Q$(j8|@?+ z%EC-`7bRFJYp`@KFjftXP+PZ)5O0oBTL)pXh+#1JJ3VY^bHpSbl{ILfrx*z519tx= z9wuu*kyR~*YASnUAkuHf8Ur1W4}|jvzjI;IYO*}}P%dgfHI++JggYimLtoHVLUc{m zAP#B8+iQghOfTq?3AtU&`M z&No)ghTf1jVpc|}oks^&1YwpMTEQN+;|PHd5NOLxhND-E5ns75iQPR#sK^>bX%v{< zO_*sxUPQ0~4f%GWAX8suF;B-7@C@TMm~D`gC<@rjSQe;a|wc>N6k^u%iMpUOpsp-h6PK?)Au@abi7!a~~19eR0 zdT(Q%%1;Ec6@<)izsGC&b~bqR4dD)LX{cKJ*BCj9H5KtIzyhoz_XGMW1p-srWQn|T zL$SbSVor|_sswA-kJG`F89{l|F*P|!M?lirf>$1|?c9#jsaEi`=}5=#kWVnK!o~F4 zk#(2m;&$sr!7E~G^xD{!Tjt^(>_fq;04X6LMm`23mFx6%OpSW~LKOHcE^|JlBSoRl zKMM-^G9icu7~6>z0&+vu9`3YDVi+6A9bqP>MymJARw7VriXhYs?;M;1_gxw?E%@)% z^dqy|*o&q`a(0ik)Nr>c@Q5Q$KArWLqoNA>qCD#n{mA5Ns;4-~^{ct`o>*z2gnne| ztWOght%Q8FSfl*EZ~!32HyO<1hA#sTukfbJn0RL|OBShP+F(uPk;CO&EIQP|#WZkn z`Qo+m{v+59v;uwd=({ufo%yhcZ4mioC+xL+#L+VHJuY<66VVy;>{ukDGbvftz@B58 znUS4XQJ>`s<9MSN+kyR#dCf?bvBz=4L^qCYx1b>1KeLi#M5(d{O`yglx6^mdycn&c}5j)_dh@m#taGo*| z^BlaKA`V`XedixG?hwES*g%W42drQ+gt7*yO(O&r=5vshyU0)0U9=--jL`bE zW2r%eM&H#}5hh-gajxJz+aO8WbO)g}Ix<4tE!YOTUi*Wf)X1XZ2;m!MpIl|JH2GJ>w0XB#X*#+Dj}f&nsu z$}HFhOOU;nlTE}q`S+JNbIKQo2Nnl!2}>W7f06CL_VHFP(9SQ(YUgRK8(; zqWt^A3EVh5E6v+v_Bc*9dcy_hpo{V$>$LHI%sCV>)3mZ!NvY6O|VxU8iLz z_;AK8(-c0m=RPZ~`=yaeo#IZYZHFisrgXiZsYhlGpo$~92H!BUW1*13;F&W39~m)2 zvC~Nyc)TAeq{dIl?N*Fdw_-r51J?Rhh6qPFwPuPRfegNt8?8hON}m8>=*e4t8SmZ z${&cgF$g5G+en1cFy$yUMMfl#$0)a!L@N0S2r5nPuIi%nRq8~9%SXPiD;t%{FH<0T z$`IUOjhzD|IvW8#MjSv~f{fGcB!Y0n4H>~rnY~qnJ&|=;DeIyDm|ZN)Hu;BPfEkrb zFe!4F4+fgbQxUAgsRV~KS}a?!RVf=y$P?wv$Owf6%V7svB_HT{ZpgH7cA(qxfzEj6 zc4DbflYao<8(Pp%0U&y|NY>Z+sO(MS31(k9MPk!NTs+zL`{jDfpOvXTKrkW zu53Az`FgQN8Hipo3?I8eT4th!NRS&#$TQx_kz43%DnCN_f?>Sn&Qj};rG_Jd3;>gR z=!^+Qb|O2PL`Jq8ESta(YhoBvxsdtBz;=ts#+eiWSH8F###u$<)E$?}MzF-oN!F!o zvYeoE_7Y|=iCr8nAKAbuw^JA?i-^J%#OJtZ%xnztxk-MeaYQ&sluQKWcb4;PbCOUY zn>0!wKlm$b1{eR&7qD?L+lP+*Pz{C65~0FyLeG7V7I0l3J15p%e#fy;m$nc@#{^@@n=C?(>yf?@RK>f#=$uR^vPn;*G=@=a3tVpHN! zw0vUXHJI2NLT|$%GX=7%EP4ti5Yg*_Y%K!9jNrQ**&85p4vEW2xk85i$xL zkWa}s1+vkEtdv8BP^&1LkVPO|z|PDV$o8}1pg;j3TM1;^*Q>6*J@e4`rbop(8uAT^ z{B51p>+yH0)l@XZeKF2U(yDwVZX7!abCDEQyq+5;N=0!w!nus#t(@qzFCh9j5xri1 zrjg=on2#U_!yJ(-RfGzN-b_TB$gK-c6Ud&h$o`NcI|ibUv&c5ck)?v@Ni4GWa%5gG zCS}J6*=7#eK_Ht*$PDB`w1^Qv=1s_~=E+PMZyAtz5HcJ2>0a;zOM(5um!IZD@1QY- zxUOIv?K$*V;lkOZ!VBd`h3Je(CFub|Un^H?Lj-Xn;4cvdk_*VDTda}b8Z991=q?@- zBmWX7-$RQ{yqsix{`~?Hzp+A$#43YA3J?*IeSy~^$=eK1<@2`Fyf|teKoeDgTNEf) z7ej=HWMA4i`i|Rc{ogN^8sZ5w3?0&G;GU3|O@A7vLC)sCBUBXk665J^gUqp@56E*S z^6ry!Bp`&wY>WtKD+dH=?ZrhA&i!&A695&nKpZ9r%Vr7z(HWa|Ff(C*`YZsC#r|@x zFH99G+#@*{%FUJl@d|+!5&T&`sogkGb(ujxQ{+HT35ayZP<~z&=s}x4k;qGNt|NaY z9#wizw29E4pX=z2 zx#L_+f?z;k4`Qi_d|IOG(h!B)B&+->vW3JTrl3?dl5lZH+@IKRig9PiFctEVGqS-A zT-+e~Cl@iJ19h)sry&pfE5_wwps6%au3TLFWAf3Smzol^it)Odh&d=Ia8R9SbIxYsOX>|3{=TA4h_RWDvt}+Huq+FnoB5 z4~wy9=Jx>Abf;Jbn$E$^@Nucbh%d`2Oe9n@0VB}k_|Xrj5NoFxwoGIhLZAx6g(Xv_ zf`Q@Oz$>VSNTBgH0)0c);mm{+o z1!Q{(YBxs^PL`ss)Q7WBHs;(Hb-AA9s{)Gc^Y0gk45tQMc*sqrz}v`N8H02d=D^^j}cW3Sv!He+cTedIhe0T0Eii633%1i52kg2zN+FAjZoAX6h`UL3OR zK&DN|N;za(fXsxDNjYRyK$g!3GM=U!*%d<2c3n*u7pfSj!YU1WDg0MsT>)df0+S< z>*^YyyQ$@{dTf_H%ioQg7xtI>A@_AwVV?GjUJYNk z*WLc_uKOoitz9=Lduh0z)rtelC$4ujxjKAL`5w!6R~L4_m^kdvo0+PM6jByj7Z^;R zR&awE+Zw#d^@eX~YIAejhrF-p%|}yxC;WJ{Fz%~a(RFJgbTxEq4otP++H0YLJ1e<` zn9Vbo-H557M$CHJ>6j1`-r%zY0YNbI0GW;hK+hGdmeTNnDgt&(qzc8nLY2AI8c3SqIi0$Xojv$5i^k>NUq`HGyeEM?e;DgA)W zuseq>9@uCvBNz?UI88eoH7_S-B5{CX&n%lvh)7%kF>~nZig@yj=JqGBMIe;Ml6`uX zu`2H@xWu!TiWAqe9HW$Y zB%=sAhS5NV-JvBjVU`4@5C$x{+w#ATBK3W9ZVC{dSGzKAvvqL*@Hj5<`L8*+A-BNW!A$zE`gcCxX-Wb^yWC+iph8kJ~j zkL62(dFcjZ$mw9jo^oUvp{f{(WTeeew0MxFvdtzMNlQMGxEJ()CW)

!f-=LP@ln z^l!PGlcKv2Ku?mJ%?LRI_*#IgBW;^OdW}WNm}UbGw^NKGc~@d(h7I8Iak0jzlAk68 z)anMf#dH={WTkJV#ms=32xigX$YfwUCBn$EZb;zDk*N)?XyKNVxkX-?xU%>m&>`nV zA!9k$NyE#DQKggROZmcKt|sMn%7_5RZG>E>rBE+bT&^>-;2T)ND+X#R%Q`LQI_Uyk zBJL`gSunG*PDJGr%+9czoCWz^fNn94&7!|K+3kl~ijDwX#2BuVA<&Ue6D{VLX%VTS zC^nEdF{kOMKvOY^wANrMV8aFTL+1rG@){fH~}A-W~?eoqp>>(Pz9Clq?a`m z{9#AhH&BI9L#~ow6^Cv$p+gcEBjse5Z$)s11jpGtlcNL&g%YgdG;=BT(yV(Ip_z?MJ+%-9T^9gPk;Ffp5(fmddzVp(Z^}mtEdH?qB z4>~FuM!Qifa=-iJZ#_|hG3OXk5<}+;C?POHDbv0avwY1;Q;(L0YW3D>yK@IX{Xlchiq1>N5>|PMX^Mm zoc`Wc{#EN7_8mCT-8@2&$s!;sk*UEPnQP#25via02d30WkxDq1Fjb!oYC2neIM3g} zw8+0U&UF3q^mfhPb#?cnhy7X6ccAzD2?Lj2T)t$|P1Hgaqx@#C-aJU`9ymBd zgLwm6BwBs2>x#

5bMtn>S|_XYDJYTvL0LHY9(JSrKE__HxC>ETo9{UH9_Bp543E z2V2|^YX2RSgWT(-wXa$V&YJbD{qnfdcMFoR?>{;`w$-td8jhTT7@xF1m@AoS9I(l18Y1~Bb)#(bBGw=WWl<9)HmQhm!kxgC_X=(gVUcS_3G4Y#VZUwfoH9v=7s^#K})c#hcqJv2D> zq+i|B*$>Mq zZI$TZ9vvNLU1cMr!rc z42!z@`mA@U-X$n>*_X9XGw1x}JKB@KCC%`!JEgau3MsZgF9)W-{Byz7zUB>gdlVz9 zKMb+n9OIkcdTp<|x{R2ejN-+l#eX}=C?Z?#nX zYvC!2EF}7NyPj3^%yLu~svG;AUwU!xw%RXQRMqnF>#8k z^P_!gLge))BMfA*apT7HT4X`<&Mb)k-nmcWTO{}uixj&Xu1^l@H`d@wUi`*=N}_8z zUwSc%e<6W6%QwCGJ-f`10NvO8YhHUxUI|K)_KmoF%PYT6HccHR2;oTKPOAwD+oiUT z^316urQ!ZivxU;i_s^gArecC&eP6zRKVap`m92}M8Jj&;ubVfZQpXlq-~QiR^mKeO zda1X5c$f6#b+XHO-K3c_TYl~`H_}0)zvq4RK*5eQTCc8NPE21EX-vr8ucHZ(9-um# z>eRdd<4Z`>j+?rD%aqo-=her5_zy*KO?+8Z)&3ma?M7*7X%9YhO?y&T{`}PW^CM26 znsEZE$fz#l0I{y6v9<(74|_NIm*|Z4DwW2KTelR|U;{l-YNAnda~`xOmj@aNf?*q3a&>N?NTrz8 z{4G??d7?x!+q@;y{C?uXQxD88U%x*7$)w5)4X7eqHmUi1deiwErID8N!ngbR6`yx# zT?+*_^~5!1wwrl#q2$#zRN)mP1ApJffXWr>$y?65q6j1z2sCP%h)T7?Nxw(hCh>?p zM?EBSTPl{N?;U{T_l&&T4aNIP&Yizh;rkLxBfaJGC>M--scD_w){i?z2eq|GQuI)Y z5;$i^8j-#kmhojRsz2|eGEy7Wty}ynR}}3wWrIFIcc3eWtCiukWyd z%1kA4TYo&At+#*o?vilxz6NnvuCqagp1Ctg}e@!H=QycK6O>fYHO?Glg1&#O`EEO>VEyoRvZ7W zC)L>&dZWs%Z~}@`OWykyjck4mtE|!-fbEuJqVQf*^T*ESTC_6#QKzd{2kU!ObJU#% z=fXzZKQDRyDgZJZ{vrG`)%?xqR)9=E(Gy zcTt{iR;7S9_>GSxI8;Ow{N`fizUmlz?Kn#9;lxfUiZ7?bkzI`-}m3s z+uLrSinvwW$%cwI?!#!<`%PI}kVszWg({I?XT8Dwc+gRE%zwcsE%=}--h1B zFxA5*$3s7Sw!Hk+c5~i8N!F%?_a{HuafFuJ>`)`YNh-uuSND7{?Ckv?tsj28sMBt* zcW7U-#ni8^Ol#cQ0*iPm!7-sivZCG7E<6m~uV-#H=tEv8v^c=HJB~FwU%0)zV3+LQ zSR<+3+#Y^0OYqyJF@Kj?>0gRVu-E5>Bl3JPYhN6c7r$}dvnl1( z>az0k^4Knw-@jklTC$3HHn{iF^ba3CG{goq#!70jrp-dt&X3&0&u_JDnc5}sbNWIO z^}KX*7~H)g-M%Vx@IzS;;n33QF`BV#Ij39 zlZ#$YEmbaobv;EqbHlcNbKF?B`Gtkho&l4;-#E4JITCaj znrD?T_%r?9FB>WwQM-HS_~rKYr)xn{47P|jK4w@DR4;;}+O5Er61~{tZ=KiRPD139 zyK~$sSkb~l)Mz_AUze(s>Q(KF+ zo5P?}iPI9AlfPm6lnjG+bHYeOry{x^PWm$OOPtGtC#z7Zb#B!u@{=eV-Mex11Xv>H zhM%8|P56d~aW)fzf`rcxW0!r4c$Jb>L$3c$L)NHlY#vKGy}|`F&FILwdpozGatRAATX z74F1&=@{xqiC+6wt78+AiwbG;6V&!KD73DH-430MZGNT@jQAAK6)Ltw8^+~ocRQ+U z-U@AV#+w6SaK=xSE+KPW5SY93YBqKx%?Z}cO-;@ZqK75>)mJFQ*TSQc@90ll{&wa0 ze9!!8oeu>(IdgP_HuQP7imEE&r=n<(9P$gbZ8_fxQ)u@x{Py-OF01oStlV-9jLgCQ zVr|n{JeEKv1!sqb&;mrKc|pQ^%Skr-CwhOmPiKrt+fjL23||(GFVUeRH{Pya`snER zQFoeNT+Yrh`;~kH?>-d&#+jr9t7R`elrdua`vv~Ry}##lVks#gO0KMae5B#a`1F2I zoAv!sILPjFfIy?YTCaU{SuJ*!diyj-8Bf-m>gQt}bO80>!^yJ_wg}rjDyZ?{ozZHz z$Bxh&<8*O6?49k6O-??(zB#msRbQ9lM6Wh@V8fV;x|qVSJ~dn9dDNpGQNM>TnhP>b9Z8c3oC&eUbX| z{VJnw@dpiR`vvhx1x=fXC}3r)oSDVBzN0sOpDG;$&RaE`GQ71~zQx=O2BCV=kS=8sO~uqoOaY zM?l$(I}_D>n##}Sz*epD^T&ySc4-y&7~y?Ph27(<+2>l2xG>rAZl@JS{qRUaP8J;A zvmYKLdV0hIrz(4%I9}f}=DtGb{x)x%`;A>~^(fxFNJE9mMToA?lVcOThj+IfG>DQ@ z7Wc63g%l-(Y$mD9)}3hjkWih3ly7QENR_v zNmsr4d3>9X)FBAR7SEd8p*h$xI9C+>BQdl_h=u}=jo##f@m|7%CwUV}R5d#r(xLbu zf?XlCzCpizJ?|=MMjzJ;Z1^-92WvVgHXXmb>h>fvlvZZLDWM26C#Zi>d)sdp+CoFe z>1Z_bLV(*#l1^RO)8N3WWex%34!{m=#bKfZD{&t^>hhtrPzWbeIH>o_Np$wY24nJx zZC?(*#!{nQ-RRYi;*PAU#F=Wh+lg&wiVF7(J)zZO%1@DsS{>2&6c#w7w&MKd?Ac)L zR;bb7LC!cK?S@azDfs747wAK)+3&~Wi^AwZnd*ZUY7MC-Pq5*1{`NXKVexBvEdUDi zEw-1UcpRN&uty)cKH0~|=RSOaN3zLXY{_)6F4y9Ob;IXcmd1+vnLW(=)B83{E|1&( z>)QlW-8Z+-kenJ!tGOh5zjyMENcj4)ii+VFXv-Gj>;{}4Pvfp7Bn3VttNxXP!y8_| z(atl!-Ku?3O`gomjimbAdVCh9Rf1z{m}g0L79GH2l#SHYgKCBb^`du5%B$%_K?Ukl zP#28Tj^YumJOi(y@$>ZOR$X1zOv?dF_w?1;~I7NzHjj2`>z)T-t@qMvKk?T z`3Y!vT#oIG5Ag{GbY7m%ss7 z9W@f7PR13oJ1?Z(`nU9-Xgq(5qUJyBenO)tA>rmMkszp*s9 zu(|Em#&**)^N%mDc5%r$Q1#n4et&A^rJK&h_xH3u!R9=+A$LJwufekq_$2S>1$Vah zL4=awEjp8;N?*ggwR}NIY;4urXSW1F^@WBje5&=g=?dRfC9C zB#~r16mlQ?oA#I|FkU5eB%@7^wjunPSQ?u8hu~>IO;r4a?Kg$c(9ur!cE<6Y{!%%z z5aFkiobd47zf+u`%<=ma^AYI1c(u(&7(nk7h;)n(QQU3B_KR?m&qPYHm5O-2*amD` z4mQWa{bLeI%)ITtmYIZ3juAxQ-ocZkTIBH z;GU%K-@h-Kt}4o|sI1%vuXowRvAnEoH(pP9DoV)BJ}^K>UH$FG^Z@_1+W7E){ydxg ze1ZAYQU!YVNe^D2+;bHHnOHFAmj;U^LZtw6Qkg^fehXSi15@VffPtrWJ znEgY0ztnX*=4ml=ChfQ=#Bl?a?Jh9nI?uduU?-uH2im>+H3o2Lsw*EI(Bz}3LGxw2 z-~bRi7kI<#zuMWgH=PIeJUul#hjjeZ>~P(>TB=HKKpnU)-UwPz?m_+qd)mR>%^Dmv z`>=zoqlh#m_w7q|$%$FyM81|Z*I}Nj=nVy;l5=!dym2NL*rlv4bY623yO?Z9azJ}y ze0#s68`9R0PsM8vy_>79JNRt5q^6B9nojjQ@4PI$jnRjE;w>a8s;iIoZ}`b7yG~Jq zg~!zwON|X|&R`dYP|Ca&M#qmIuf{p)t+RqS0vlt)3y2HwvcvTp{H6r9G|-O0UQ0A- zB_e@O-@jk!_YRRCIMGc`%fG>69Y}9^n2vq!Y6=Lz_MN|YZSV0dZ-(Iom;GBCKgTu) zwBZCw8W5J=urK}R8AV5%SN7i4_C8N%CFIa*B{yNL%7kF=CqP|@f=P^(?XO!cH4m%l z-7W;{dXuBXqXAh6*J)6GCcSVn8u7b^cVEpkcj@}M({mukTI?3G440Im>9}oj?=O#D z3sUFDGNM5`6TO6_feAU5vBvwMUU?x3iFXl2u+fG)v^qQ{OQggbNa5x+FgBi_LNv9L z7@Mt#obaI772MrM;-d&929aR)1WWg1q8%`g0$d|>-1qSLxROaiCBLWw2Nb$FW0bMA zW-4(Q)|w=zDkB?U8GaZ#l=6uv7N6t^Wa3VMyNlxh3m1wjAZ9pxuomTy(|wO)St&}& zsnd>*PEQO}7*(RlTt2v9Gvw&l6Gq|(cPX?Or5TJe0{uVDxK8yR5+5+y|_s{j?0zAztG79M6)btf)^C@6=sSC$**&n0z!8Vd%!+> zwO8{55qDxX0Cf>y_UkDvs1uA)hx@*i$&lNeA* z#k+Gl#;zu&bjR4-eGjuA*LRFIP7G2?Z=~B6fj3d9h>^B+phJ=gQ&dI`B;;xIuuk?0_SD%Yn}wRkE`AZ6|yxz*i-|p2hVl!67GCe4K-; zB7Dn%&z-6cS-xy1d@8_qu>;PW;06NjPzN00dqemrUx0-(C44%-_qgEID^0qQd30jx z_9}-aWKu_hbfysZfXsR*fyr}joxx)1Br!v9ZN*43p|)3Wc*>{RS5}WaMnl%7$P3Ws z?wKN{df{>v?oKe6Q+G)Y;Dks$rqX9QB{TzNP(tNAbivOEV{zljcIv@(+5#sfQpFlP z$E{_lDXw*-Juyvfj)HGUgphSYoPbq#sTo#t#5BC&kj<^;n!8w)W57c#?19-;$ulp- zvjV|b0SMd3mTnDAPZ;N+l{Ao};0`e?6pTigR7Peg5`|@*I#y*H>3e@H3Xb!QFpSJZ zaFHCQj3nd`F;a-tutOGsG`ao<)q;z;RZ>bfbfu_%?t?6N8IkQoB$BPgwjE=WRJ|~h zXPgO`6HH)8A}9O9nx%;Dgbm8TW=@a|;te3Hh}>%4RE}>~Xle&?L#Hk0hAu}Yp{Re9 zTJRN)BhN_ndSN1s9mt}9YzUO9rURKVkj;T`ODX4tMYfWV4a8#t<{ijL+|z;Hp#vF- zZKpJJ+MNz$G_zi~7cb+e}wb{f*;eAG#qO zf+T1ORf$tan<-w%lR#dRRD+XjH3_X#gj(<~PO^PyYJD(~D_r)5_y(|vQTHsS>~q!t zX1RjMZn(@|&RxRWcp%nb!@~mu+$B6iPy#?*<)Bzxk!rzTI9xr+j)q{ak2%eDBtv5e z1LbZkCy}XS!7#II4lKzG3^Bw!+>N+SI=K54A@x5vo4fE&8VL2rG)zQtd5Gr0x4>kw zPoR*1L_2U!WAFe0mz2UN;>gKFLmKrQ$*SbX456MK9`UsnC=_Gr=dht-wc61I{)llE zW;&L1fQ5jGHVF1Ab3$}XECt3fcoSd(NAnC~q88*D%}vZk4HI(|fQgi-$kMnLZj4a` z)7rSP7eB=MOhU>F<=en!Ghz*}9+9}vg+6j^VAdkBmGQt%ufs!%mqHsw15pc2>3ggY z3okmJ3fe;?tU@{1y-hE1)Qq8BXymj6u#3P1e}67U@8oBEV^-2n$Wto8DGe+ z;kFxs6p|aNGzb6a=!8rqM(iZaln&<5JtlM}Nb%an$w^#DvkN__Hh5zP9FmS0@h8A3 za&Q+N2@YA_cAV(MOISGItK{IW>?OE0fa{pvMHU}&O6@xEy&$+Vz_+dg?g+t^1FjQ? zuZqQo6!t34N{UyqaFD|L4mfsxP%-z;9dLy23g&mQ1MWS+#h}dmMF$)^zt4c9q-g84n>;CLfgIN&Sf;DjMW=X$^?cfcWH!d$`p_IJRoBe-k8_p}3UKfzT2j`Hed z_*gnIzj|(EMl69D61;`Ja(PI**2U`Nfoh#c;3Ry*IFC}+sK;>kvOlzNrpFyg~J;P`kV^aAlHeJ8WrLx82Pg1 zZKUZvid1u%(a|Xziy>m0#Azm>gFZ6S!Gx}sEo5i?cf>N9+?U)?&JFV}6Ry;OI!n2< zKBk7Y;!?H3qz<1K;Y&~(q@jH&d!1eQw^Cq>N)LG0a%p{RD4&U#EO9F5R#`+hEFhk* zFtfu8^7_)cdl#u@N%`ySK+&{ZQ}uOXbUB9n0}sb|S(+MgUXWQt9!@&7Fv*1D@1qvoD&|llsU_uO$-N-%pk?8jup-t=hEEJtN-FhV20@fvbnTA#&8V4 z#C&09htCr{CF2}2Q0Jvz2a$B7v6&!oy2#0&At;eB^9biDnRP7GV4au#9F#3oO866v(QTB0a z#MJefRpfLlJo-SIq=k}wSB`G2Nno5v0goAQ+Ai3P1<5F*mYwqi%nNeZN$L`(bsdxQgHA9y z1E~s@^^T%9q!J!G*=kTa!7*z|BOG_{Wov%fMJuQxibE53v!$V|*(yLOA$5wQdRtki z09we4|1tbeC!En^S$*VpQ7DG44jh?alM_*_lvUr6`+(oA@X4?l^IqAmg189AESw0wxlbCUaK( zXCz(Xq;jqcTdK^Wdrau^dE40XZy8OgES&8swyIS|0d2EH$`5D!3wM!BX};sO|KJ

PXp=@Fd9n%Vz>V{s3v`VZV~7S8tkf8fknxOKDsg)8LIT%9le3&+!8 z;pV3N3nxXgF_QAvSo{YLskKbL!Q=nHtzhBoPW=atm5J%Png4+sLU611{|64qGk9-k z=EZ*nE=0O9Bd{ol{8ufn(Lxo@9QmJ4%))6K{;P5=NGV~YiSr7R+~NaFaqHv()tiYin9_ZudKOC_gkB8Ni|K;Tw6DWw zc8s@toon>;#d2B!HFWS5WIv7@J$TD&r(J<=0|pH6d^qwtLY_nZMl{c0!3lfqBnsRk z`X|f)p`Nse`(1Z3HFB=fc&I+R|EFSR2(JGbUOZ9l<(%p98ZkWGC{ak+qFyyb$T4)OIE!h=`)R`iLO<^&0X}l#4HFq4he4W!^Ew04T|w z5&4kYEu~;9IX;H6>lrq^z6Q>U;f;jpX7lEGdc>k<$n0h!yd}8NfdxiVJ{d_$y_e#A z&4^6MJW@_Qo72vmM#eM4rq35*OT$ENJc?E@otLux~5@@y(Q^Nf|f76_$7 z9%HCXz3-#<6?i*FFdFA8W)2Zbu8!%mi!|(S%$och!{*Mj$@BT-70g-?0140qc1fe!~F$%a4;_!f5Vwgk36y6|kL74(V83IzS-9$JA z>KLssbZ68Nl|&99TEXOp1bE~J&K;vAjV7*8QamY6Mm~8U%6`XDlwoF&`=LrI5fxi8 zj)^pMB0?F4I7`5HkUmltynrgN7#lTm1C+~d*p2-)vd@_eF@-Di2=@bUXmrLC@gO(avx*`j5 zUt(kGPKuVV%63QEJRQE6w)opO!|NrN+9$M`%?@?@ST+C2&68zo?=AMQUb9Cu=FTv@U5v?VYAieGe2ZblqN{L~8k(|C5p)dTrn!vFXF)%A@ zRva+cVr^2wM5XbhIoJ+}7qLJ09Sra#7;Z5;+*NWY0z5sbaER1UFfx^tbP06)GwHP8 zVm|^O4)Fd2F71>`>PMP}Lna=y%fQ-Y)*?JVhgGGeTGSm$_`DDz&W_xiz?yW0h3>@? zgIs&ZB=%=*B4!-sqRPI7WXBR?4dlVJw2`(<#Ngpc1w~>F?>wJkFa$Gqv7=9-(GTMmo=##+L=Tda5Q^H99o*~&J9uvBG*42@2u%yjWDF8d z_98sXh^El_hZlv=R2(~4Zz5f4g0LhRDW)bHOH6PE&~)@-B4)FLBf8NC<-K@pf|XJ# z!h!WD-NI87V~8{r#F66J(Z&)Ry%6QEz!41HzmQNhBRT#vsOwred?8ltiVE7~W8#wPFw39mJKgZ&%f`v|pm` zM7vEm+a2K>sciIv)V)9nKO3{)he(0bFc+$ff;~Vu5=&GXI*n?l7@Is+fiL0bE$nbV zNJ<5;7gEFp7-p}4%t;#NR>*3YI?MN!h*rH}_1%!|4q_A4*zPmg?uhqYWyN$*);$W* zHMYAu+a1IvvU1ovhwY9`tQTx|byj5tf|$=NF^fs6-N5MKEMo_;wD*Km$B-Nh$B& z54nJFQ7>Vp;c)7%HHEqx;)Ooe)rjtT!`v}j1g2qh--U8WGItr>@g`*-ni$`QbTLU$ z;?##-;;P4Kbbc3!Q^8moZVo&8G{j`6ric+tqS3Yfz_W;lh)t*2(L*u1AK4cslkGl` zx)aAUe$qyjuH+VuV&~RC%H=4<-x#>aW~U=net&)Cs~lu7X+J-^fNsjWH?Bl2#%Q-E z$0i^T&ly>zsS_0u3PjZr&Wwnn!4=-dm1uRaUJEY}iPAYJ2x$svFO6Dops4k`@6&hh zrj+^8UxGQHq@rF}zN~<_$e|#6hiewa-&n2|FCtpR*TpZE^ZXsbcxTz7Z zM(b%qABgA-=bP)o!}U zos;fMDf*NkU~O&PdgnTFQz++}vdu@kK8UtWcAjNo`lUAVDhOc}_%mLCf+$xan?t{I z3?*s&Dhg^>$T^6l#MzdXmLBhJc;5VqvB+S-iB$}D*CcpKu&pMzr92av|lN{KFb z`BDRig1y<<*~BB(^H`r(L}|?#q+om5z81NR>t7LmBf(D$>)m-!zTN8?;~E zq{M-^DcihSkes#`$#X`?PI9>Z6|w9XQh$Lfxm&|EpC24b#$Ui`z%%z+R0dG9y$uiP zW+UaQ1b@I_&pzWcL)3{aeno&wS-{WZCc$FNYs8-e&_hN+|CbpOi=E;=WN0g`^rZxI+etWBHW}-eRW@2|zOjou@4k@A~_)}ToDNiPipufJLg{*{= zPq5b6osFo1T&nm2Wgt&LrfD&C#owkeN1pDY8$0yz05IzjL=~RwhHK5bb^GwT@L=R| z6yr~v?2E@=8G^uV%Els^e1xonIE`g=7XEaT3sNbNz%c+xgDdTkozh)jn3;(99tfoC zY$mjdDro4>0-zYg`P01+<(O~`!yY}wO*C7c9kS}|k)ZEcvVVLw{=AenRV0<*Pa16D z>%$(+Wlez&`IAV>=0xg`3kr>>q$Qh5C6Hv|%U5g=nqEjvod_d@fO8US0jp?d&lesw zUB8#6Ls`j`i;uFFpwUU+?hj3ILa7m624>A^!2vR-s(92-rPmw{Sx+pF5042x4-6jDVFb?@tO}{9ug&j+fq<*B{ZUFmI z#UL(ys006ga)N*qu&H@4v-{EEW{F1lh^xZpBRCz~i#-1-eu99@J9iy#aDgJ8IE!7B z4cTPiZ!vlK;|O{gsS~OY64TnPy>D{L1R1jeLiHgf?1P|-g|13W*Km&_23CJpX*sD% zhz~O0k@#0bqSC;?K&j^(+W#B~eeqe^F*IVZB%~w+qJ3MfgHgOat$30;6TY7HISwoL zKP~FQp+(|ofu6$VJJZfw$a5+osp)`%J~%#Y6K}}WjZsW;E2+tQLi_?+E7+T3?R01i@y71b_VEiHW`L2A0@&|c)x zct0&#A}}9n=t9;K^~U-JJ%$?4R_DX8$9P^Udd|61%$sYXW@lfdS(u{}p5|wJYva)f zZw;Xh0rOIS9AfO#%rbQW0^#$2Ml#TcmIbiPSCSi-OERbH{eCCJa{}NCMg##vo)(hq zBoR|%&eqmoK<&H0BvP&~(jZN#XJ7-`X($f1ApdbSHE7yi;ZV3$mZy%Kt$?m6 zR5Dg)7rHm;qW3!qv(*gg8|G|mXjnm@En-(XMSx_Dm6hNcu*RJLfp-UKp$$^!p)y>e z4BH9mg)7zuY*6jrps zf1GggIk(O%U{KjOd@Q_>6LCagr`Dfy&05@ zhRwUsA);0?3DmplW&WFM`C-cd8?E*9^!&a{A!&L$#b-IKCStBX4P)yo;aO76FCEwv ziqzqFYTbgns;FFiolt7f07jE0O_nWN=8Gh_N<4=8JE%(i$cn%;%52cl)XtkgK-8bf z7k;e@Y^(6$;f2}~;C_zziOd7-q~CdZ@_~6U0%A@>p<2tp*timjy%5md-LVkRrlAFP(rvWfqZe}tssCN@@L~ns3Q%t zD+$RvYRv5DhyibNtGfU9w$#=jE#J|}X{@YF61!9qJ*Hg=FDy-DynQk8v;eBFmGDrH$qeZZygbVB(*$LXb^%FS;JCS9rH#7&+2i za%AKcjQk6u++aq2h>;8KMLUq!keijr$dM3{%qCmm4o0Wkkju=BS-ku@7GyEkSnwO$ zVF{Z%azJuR*=P;KRg?hooXKgr7_AN?dc($MLVH${cS9dNC#OowvKSS){b^5b$FPYY zMpj3w!fDDz{=&$cvJkk)M7Ao2ac`cddG;}%qK;`@?R@|PWPm~dyt4c{xDR?q~)UZy z?(ff+P$&R;^;x?5lJsn4lSyx8bB|uuBmX>;Hve|Po;5OZ`&c5^_uf$}8!cHvld&_} zAs;=NjsBa?X8G6a*klUtZIw+X%YaR$@bW?VXGK~XyMSx5YQW3*}$WqqEDYeSJ^1 zk9JFmsX2#jz4FhSXsZNZo%75^@cu_mC3(YueEu0?OVb-6p*hulH~M zA1;E+-xlDhyk5hT7gt+bOTCp##%zK3qtaYd94IrhVl6b#Gj#=YIs|x|b&)`rz)>F| z_Yaluxe6C>!voDPb-qlQ1qZORIuEC1iA+-sBnT*#7wAfx`gL|$>wTzjzxSUZJ+b~= zaCf#Z-20WlTh9P|bAy3V??|G~wo6+MdgA|IFx}Vi3EZuTW&;~j;Z-G6xX-W<9(4e0 zV^QF!noUlo()%GiK^3CzqEyZkX$ayM`UQBnGfcr>m4Q5)$5(bWNw3?J`QX4b!jGOd zHEp&qCZ%`>`I5L*8SwIme~ONdhWos*oc?l;&cFM*?Bi74Ceb#mdz9<4HzEXh&VVWhC_U4)WY9sj7QBE5-l7DMo}w`r;I7=V7mJY;O`$u z8&M8yHzE!PSL4Awn9Y#V{qp6pp`oF0Q6}lVxny~AhSv@}-;`E~(K`Olbn_*$4-`A) zva+|PRSvwgjWAI*>$8f1M(j>W0Z`!4UHHVf*~cZ@F_0Ov0Su`GMHj%L!_A#nF>nyG z$-FMw>*`UIm+L71Gn`7?qfOZ=Ld%iXsCIy^dxyuV;DN*CuPWeCnw?+v&g&Jh-URPn zjbKnae}Ulp)3mXM-Z(2~-SGXK{S=`=7-_KjD1iJ+sqzD#PgST0o=t@`P!MgAV(63O z8#LySQ$5x9?i^((>sfP|-IapKj<~J!Dx8fU`oe3w-pR=cK*QA4D*-E2BOEpb!dO+c z8~)##p@daGP?F?Oq&CbH+<=?+gaSmFZY{I4bf_%W2#Ur$l8#bkfY+~IuLO>}q3GLf zJRCvhl)rQAqp^us#rvBiD9IO}Hr}x)Ohn$#pQk3r+<=2uo`d-<)(lm_`dDX++}2R> z1IqOxTzzSf-%j`lsoYaCY!>agbLZ~vZN5(E;MXl7@DH21kio|+r<^M{Z<7;K?-gCL59+wfem6SGB3~ z+(1Y|LP9^nHx^|B$@xm}!f-MffEj4tXT#p`da^*7xBp>H%w8$tDecY`d)-%`qXYcI zB*l|_KGklRAHyqr_S39BU=*(uAxfv<#raLjy)Fs0-afmHvJ#-B-N zwR6a{IoaP{S&5K@Y3^_tAjk{T0s{g}{IN>fvQ<)g8>ENoXc|K|r`@=Rkd@*zY@Yq! z*t}B^{Gf_G!=dn9mIq6*nvw+Lmc^sO7cV^ilWuEv0l!V`z5^B7?x~a|@~P;Bx)~jC$uITcr_m_5@3q zD9}9*fyj8Wl((ST%6JEXz{w-ev-tOq=rW0wX7FuYLCa0JU=N|L*5+{O+lRJ+ePF!` z<`&BF+_$f1nBk=ntexH?RIwOojTD={E_%qOvr_hRh#ozZHWC;*A)$QC?H+dAZeWxu zl#mGikKqsvc{S_}Fn4)yCp_WpVAdt0LPCz)2zW1B|9wc*N^nU)GItM;pPt$$73}(9 zvYZoGetjh{YT4HNT15u_)`};i*2Pu7v~!AzI1+W(>4Q4Gt)JIPKK*0oiyxHRKIMOz zu>45Ww%C?rt-zgU#|3d>>5~QF`wt0=MHj@PKJUu2E5w&<^Zli#Rya$>$4mEqqno@t zAmFhCQ`onV&gqlhw{Hy`^S%;|=N#XIfN9Aa$Gng0z3Lf-L$|+)`aV!y*>>Rpe^X}N zgxinyv0q(Ip46#M)U@2~;-ZyO;~^3U1P12wz6af89Fs!(zFvL`^%wK^1|%o8Jn0z} zE1q=B(Dyylm?9r?MHC!V9j~6pqWzftrBVW1wSJrBncdNg8U31LM&);BejruL26YmT)%!A+Ni$ceSmf39B z(B9F}almNRs+ohq=8B4ndMK;*vuX?R@&|)2A71)|$BXClzq+iJ?|e(p-i~@G-`#vB zsHmtoH1w)*kIVJ2v4G!2qROG6;@h}~niqdZ+j`X0%q)KPif`^rPKH}sD=91A{q@&% z@dCkJVaTwgQ=|7Ga!FD6H<(FgrA3>C&bB&$EGb;IzI#wyE`mk!Nv`0fwU(Y5uZ=V!y=KyB k!0SB;TZaFVb!nSzuk*`chs>{!(X9Wnev9>$buLH#32e}aJOBUy literal 0 HcmV?d00001 diff --git a/src/doc/en/reference/combinat/media/fusiontree.tex b/src/doc/en/reference/combinat/media/fusiontree.tex new file mode 100644 index 00000000000..34f2447ab47 --- /dev/null +++ b/src/doc/en/reference/combinat/media/fusiontree.tex @@ -0,0 +1,29 @@ +\documentclass[crop,tikz]{standalone}% 'crop' is the default for v1.0, before +% it was 'preview' +%\usetikzlibrary{...}% tikz package already loaded by 'tikz' option +\begin{document} +\begin{tikzpicture}[xscale=0.8,yscale=1.2] + \draw (0,4) -- (4,0); + \draw (2,4) -- (1,3); + \draw (4,4) -- (5,3); + \draw (6,4) -- (3,1); + \draw (4,0) -- (8,4); + \draw (4,0) -- (4,-1); + \path[fill=white] (0,4) circle (.3); + \node at (0,4) {$a$}; + \path[fill=white] (2,4) circle (.3); + \node at (2,4) {$a$}; + \path[fill=white] (4,4) circle (.3); + \node at (4,4) {$a$}; + \path[fill=white] (6,4) circle (.3); + \node at (6,4) {$a$}; + \path[fill=white] (8,4) circle (.3); + \node at (8,4) {$a$}; + \path[fill=white] (1.5,2) circle (.3); + \node at (1.5,2) {$x$}; + \node at (4.5,2) {$y$}; + \node at (3.2,.4) {$z$}; + \path[fill=white] (4,-1) circle (.3); + \node at (4,-1) {$b$}; +\end{tikzpicture} +\end{document} diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index ad49f9c38cb..7753a4e9716 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1725,6 +1725,8 @@ REFERENCES: .. [CW2005] \J. E. Cremona and M. Watkins. Computing isogenies of elliptic curves. preprint, 2005. +.. [CHW2015] Shawn X.; Hong, Seung-Moon; Wang, Zhenghan Universal quantum computation + with weakly integral anyons. Quantum Inf. Process. 14 (2015), no. 8, 2687–2727. .. _ref-D: **D** diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 8fcbb999097..fff53f6f00b 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -227,7 +227,7 @@ class FMatrix(): EXAMPLES:: - (B3 level 2, F4 level 1 conjugated) + """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): @@ -264,7 +264,6 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab #Multiprocessing attributes self.mp_thresh = 10000 - ####################### ### Class utilities ### ####################### @@ -1027,7 +1026,8 @@ def find_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): except RuntimeError: pass pool = Pool(processes=max(cpu_count()-1,1)) if use_mp else None - print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) + if verbose: + print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) #Set up hexagon equations and orthogonality constraints poly_sortkey = cmp_to_key(poly_tup_cmp) @@ -1037,8 +1037,7 @@ def find_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) #Set up equations graph. Find GB for each component in parallel. Eliminate variables - self.ideal_basis = sorted(self.par_graph_gb(worker_pool=pool), key=poly_sortkey) - print("GB is of length",len(self.ideal_basis)) + self.ideal_basis = sorted(self.par_graph_gb(worker_pool=pool,verbose=verbose), key=poly_sortkey) self.triangular_elim(worker_pool=pool,verbose=verbose) #Report progress and checkpoint! @@ -1066,7 +1065,7 @@ def find_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): if pool is not None: pool.close() #Set up new equations graph and compute variety for each component - self.ideal_basis = sorted(self.par_graph_gb(term_order="lex"), key=poly_sortkey) + self.ideal_basis = sorted(self.par_graph_gb(term_order="lex",verbose=verbose), key=poly_sortkey) self.triangular_elim(verbose=verbose) self.get_explicit_solution(verbose=verbose) self.save_fvars(filename) @@ -1191,8 +1190,8 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o After you successfully run ``get_solution`` you may check the correctness of the F-matrix by running :meth:`hexagon` and :meth:`pentagon`. These should return empty lists - of equations. - + of equations. + EXAMPLES:: sage: f.get_defining_equations("hexagons") diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 5fcba25b9b9..01016ca3e0d 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -67,6 +67,9 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j of trees b -> xj # yj -> (a # a) # (a # a) + ..warning: + This method assumes F-matrices are orthogonal + EXAMPLES:: sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import mid_sig_ij @@ -87,7 +90,6 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): xi, yi = row xj, yj = col entry = 0 - phi = fusion_ring.fmats.get_coerce_map_from_fr_cyclotomic_field() for c in fusion_ring.basis(): for d in fusion_ring.basis(): ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) @@ -96,8 +98,6 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): f3 = _fmat(_fvars,_Nk_ij,one,a,a,a,c,d,yj) f4 = _fmat(_fvars,_Nk_ij,one,a,a,yj,b,xj,c) r = fusion_ring.r_matrix(a,a,d) - if not phi.is_identity(): - r = phi(r) entry += f1 * f2 * r * f3 * f4 return entry @@ -107,6 +107,9 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): corresponding to the tree b -> (xi # a) -> (a # a) # a, which results in a sum over j of trees b -> xj -> (a # a) # (a # a) + ..warning: + This method assumes F-matrices are orthogonal + EXAMPLES:: sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import odd_one_out_ij @@ -124,15 +127,12 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() - phi = fusion_ring.fmats.get_coerce_map_from_fr_cyclotomic_field() entry = 0 for c in fusion_ring.basis(): ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) f1 = _fmat(_fvars,_Nk_ij,one,a,a,a,b,xi,c) f2 = _fmat(_fvars,_Nk_ij,one,a,a,a,b,xj,c) r = fusion_ring.r_matrix(a,a,c) - if not phi.is_identity(): - r = phi(r) entry += f1 * r * f2 return entry diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 1fa7dea7906..40ac7a376e2 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -370,7 +370,46 @@ def _test_total_q_order(self, **options): tester = self._tester(**options) tqo = self.total_q_order(base_coercion=False) tester.assertTrue(tqo.is_real_positive()) - tester.assertEqual(tqo**2, self.global_q_dimension()) + tester.assertEqual(tqo**2, self.global_q_dimension(base_coercion=False)) + + def test_braid_representation(self, **options): + """ + Check that we can compute valid braid group representations. + + This test indirectly partially verifies the correctness of the orthogonal + F-matrix solver. + + EXAMPLES:: + + sage: F41 = FusionRing("F4",1) + sage: F41.test_braid_representation() + + sage: B22 = FusionRing("B2",2) # long time + sage: B22.test_braid_representation() # long time (~45s) + """ + if not self.is_multiplicity_free(): # Braid group representation is not available if self is not multiplicity free + return True + print ("test_braid_representation:%s"%self) + tester = self._tester(**options) + b = self.basis() + #Test with different numbers of strands + for n_strands in range(3,7): + #Randomly select a fusing anyon. Skip the identity element, since + #its braiding matrices are trivial + while True: + a = b.random_element() + if a != self.one(): + break + pow = a ** n_strands + d = pow.monomials()[0] + #Try to find 'interesting' braid group reps i.e. skip 1-d reps + for k, v in pow.monomial_coefficients().items(): + if v > 1: + d = self(k) + break + comp_basis, sig = self.get_braid_generators(a,d,n_strands,verbose=False) + tester.assertTrue(len(comp_basis) > 0) + tester.assertTrue(self.gens_satisfy_braid_gp_rels(sig)) def fusion_labels(self, labels=None, inject_variables=False): r""" @@ -1016,147 +1055,58 @@ def emap(self,mapper,input_args,worker_pool=None): results = list(results) return results - def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands,use_mp=True): + def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=True,verbose=True): """ + INPUT: + Compute generators of the Artin braid group on n_strands strands. If - fusing_anyon = a and total_topological_charge = b, the generators are + fusing_anyon = a and total_charge_anyon = b, the generators are endomorphisms of Hom(a^n_strands, b) - """ - assert n_strands > 2, "The number of strands must be an integer greater than 2" - #Construct associated FMatrix object and solve for F-symbols - if not self.fmats.symbols_known: - self.fmats.find_real_orthogonal_solution() - - #Set multiprocessing parameters. Context can only be set once, so we try to set it - try: - set_start_method('fork') - except RuntimeError: - pass - pool = Pool() if use_mp else None - - #Set up computational basis and compute generators one at a time - a, b = fusing_anyon, total_topological_charge - comp_basis = self.get_comp_basis(a,b,n_strands) - d = len(comp_basis) - print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) - - #Compute diagonal odd-indexed generators using the 3j-symbols - phi = self.fmats.get_coerce_map_from_fr_cyclotomic_field() - gens = { 2*i+1 : diagonal_matrix(phi(self.r_matrix(a,a,c[i])) for c in comp_basis) for i in range(n_strands//2) } - - #Compute even-indexed generators using F-matrices - for k in range(1,n_strands//2): - entries = self.emap('sig_2k',(k,a,b,n_strands),pool) - gens[2*k] = matrix(dict(entries)) - #If n_strands is odd, we compute the final generator - if n_strands % 2: - entries = self.emap('odd_one_out',(a,b,n_strands),pool) - gens[n_strands-1] = matrix(dict(entries)) - - return comp_basis, [gens[k] for k in sorted(gens)] - - def gens_satisfy_braid_gp_rels(self,sig): - """ - Determine if given iterable of n matrices defines a representation of - the Artin braid group on (n+1) strands. Tests correctness of - get_braid_generators method. - """ - n = len(sig) - braid_rels = all(sig[i] * sig[i+1] * sig[i] == sig[i+1] * sig[i] * sig[i+1] for i in range(n-1)) - far_comm = all(sig[i] * sig[j] == sig[j] * sig[i] for i, j in product(range(n),repeat=2) if abs(i-j) > 1 and i > j) - singular = any(s.is_singular() for s in sig) - return braid_rels and far_comm and not singular - - ################################### - ### Braid group representations ### - ################################### - - def get_trees(self,top_row,root): - """ - Recursively enumerate all the admissible trees with given top row and root. - Returns a list of tuples (l1,...,lk) such that - root -> lk # m[-1], lk -> l_{k-1} # m[-2], ..., l1 -> m[0] # m[1], - with top_row = m - """ - if len(top_row) == 2: - m1, m2 = top_row - return [[]] if self.Nk_ij(m1,m2,root) else [] - else: - m1, m2 = top_row[:2] - return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] + - ``fusing_anyon`` -- a basis element of self + - ``total_charge_anyon`` -- a basis element of self + - ``n_strands`` -- a positive integer greater than 2 - def get_comp_basis(self,a,b,n_strands): - """ - Get the so-called computational basis for Hom(b, a^n). The basis is a list of - (n-2)-tuples (m_1,...,m_{n//2},l_1,...,l_{(n-3)//2}) such that - each m_i is a monomial in a^2 and l_{j+1} \in l_j # a, and l[-1] \in a # b - """ - comp_basis = list() - for top in product((a*a).monomials(),repeat=n_strands//2): - #If the n_strands is odd, we must extend the top row by a fusing anyon - top_row = list(top)+[a]*(n_strands%2) - comp_basis.extend(tuple([*top,*levels]) for levels in self.get_trees(top_row,b)) - return comp_basis + Given a simple object in the fusion category, here called + ``fusing_anyon`` allowing the universal R-matrix to act on adjacent + pairs in the fusion of ``n_strands`` copies of ``fusing_anyon`` + produces an action of the braid group. This representation can + be decomposed over another anyon, here called ``total_charge_anyon``. + See [CHW2015]_. - @lazy_attribute - def fmats(self): - """ - Construct an FMatrix factory to solve the pentagon relations and - organize the resulting F-symbols. We only need this attribute to compute - braid group representations. - """ - return FMatrix.FMatrix(self) + The method outputs a pair of data ``(comp_basis,sig)`` where + ``comp_basis`` is a list of basis elements of the braid group + module, parametrized by a list of fusion ring elements describing + a fusion tree. For example with 5 strands the fusion tree + is as follows:: - def emap(self,mapper,input_args,worker_pool=None): - """ - Apply the given mapper to each element of the given input iterable and - return the results (with no duplicates) in a list. This method applies the - mapper in parallel if a worker_pool is provided. + .. image:: ../../../media/fusiontree.png - # INPUT: - mapper is a string specifying the name of a function defined in the - fast_parallel_fmats_methods module. + ``sig`` is a list of braid group generators as matrices. In + some cases these will be represented as sparse matrices. - input_args should be a tuple holding arguments to be passed to mapper + In the following example we compute a 5-dimensional braid group + representation on 5 strands associated to the spin representation in the + modular tensor category `SU(2)_4 \cong SO(3)_2`. - ##NOTES: - If worker_pool is not provided, function maps and reduces on a single process. - If worker_pool is provided, the function attempts to determine whether it should - use multiprocessing based on the length of the input iterable. If it can't determine - the length of the input iterable then it uses multiprocessing with the default chunksize of 1 - if chunksize is not explicitly provided. - """ - n_proc = worker_pool._processes if worker_pool is not None else 1 - input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] - no_mp = worker_pool is None - #Map phase. Casting Async Object blocks execution... Each process holds results - #in its copy of fmats.temp_eqns - input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) - if no_mp: - list(map(executor,input_iter)) - else: - list(worker_pool.imap_unordered(executor,input_iter,chunksize=1)) - #Reduce phase - if no_mp: - results = collect_results(0) - else: - results = set() - for worker_results in worker_pool.imap_unordered(collect_results,range(worker_pool._processes),chunksize=1): - results.update(worker_results) - results = list(results) - return results + EXAMPLES:: - def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands,use_mp=True): - """ - Compute generators of the Artin braid group on n_strands strands. If - fusing_anyon = a and total_topological_charge = b, the generators are - endomorphisms of Hom(a^n_strands, b) + sage: A14 = FusionRing("A1",4) + sage: A14.fusion_labels(["one","two","three","four","five"],inject_variables=True) + sage: A14.basis() + Finite family {(0, 0): one, (1/2, -1/2): two, (1, -1): three, (3/2, -3/2): four, (2, -2): five} + sage: two ** 5 + 5*two + 4*four + sage: comp_basis, sig = A14.get_braid_generators(two,two,5,verbose=False) + sage: A14.gens_satisfy_braid_gp_rels(sig) + True + sage: len(comp_basis) == 5 + True """ assert n_strands > 2, "The number of strands must be an integer greater than 2" #Construct associated FMatrix object and solve for F-symbols if not self.fmats.symbols_known: - self.fmats.find_real_orthogonal_solution() + self.fmats.find_orthogonal_solution(verbose=verbose) #Set multiprocessing parameters. Context can only be set once, so we try to set it try: @@ -1166,10 +1116,11 @@ def get_braid_generators(self,fusing_anyon,total_topological_charge,n_strands,us pool = Pool() if use_mp else None #Set up computational basis and compute generators one at a time - a, b = fusing_anyon, total_topological_charge + a, b = fusing_anyon, total_charge_anyon comp_basis = self.get_comp_basis(a,b,n_strands) d = len(comp_basis) - print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) + if verbose: + print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) #Compute diagonal odd-indexed generators using the 3j-symbols gens = { 2*i+1 : diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2) } @@ -1191,6 +1142,10 @@ def gens_satisfy_braid_gp_rels(self,sig): Determine if given iterable of n matrices defines a representation of the Artin braid group on (n+1) strands. Tests correctness of get_braid_generators method. + + EXAMPLES:: + + """ n = len(sig) braid_rels = all(sig[i] * sig[i+1] * sig[i] == sig[i+1] * sig[i] * sig[i+1] for i in range(n-1)) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 9a3b0b7d83d..8499745abeb 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -14,6 +14,8 @@ from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.term_order import TermOrder from sage.rings.rational_field import QQ +from sage.arith.functions cimport LCM_list + #Pre-compute common values for speed one = QQ.one() degrevlex_sortkey = TermOrder().sortkey_degrevlex @@ -179,7 +181,7 @@ cpdef list variables(tuple eq_tup): EXAMPLES:: sage: from sage.combinat.root_system.poly_tup_engine import variables - sage: from sage.rings.polynomial.polydict import ETuple + sage: from sage.rings.polynomial.polydict import ETuple sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) sage: variables(poly_tup) [1] @@ -323,7 +325,8 @@ cdef tuple to_monic(dict eq_dict): cdef ETuple lm = ord_monoms[-1] lc = eq_dict[lm] if not lc: return tuple() - cdef list ret = [(lm, one)] + # cdef list ret = [(lm, one)] + cdef list ret = [(lm, lc.parent().one())] inv_lc = lc.inverse_of_unit() cdef ETuple exp for exp in reversed(ord_monoms[:-1]): @@ -338,6 +341,31 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): if not eq_dict: return tuple() return to_monic(remove_gcf(subs_squares(eq_dict, known_sq), nonz)) +# cdef int common_denom(tuple eq_tup): +# #Compute the common denominator +# cdef list denoms = list() +# cdef int common_denom +# cdef ETuple exp +# for exp, c in eq_tup: +# denoms.append(c.denominator()) +# return LCM_list(denoms) +# +# cdef tuple integralify(tuple eq_tup): +# if not eq_tup: return tuple() +# cdef list ret = list() +# cdef int cd = common_denom(eq_tup) +# for exp, c in eq_tup: +# ret.append((exp, c * cd)) +# return tuple(ret) +# +# cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): +# """ +# Return a dictionary describing a monic polynomial with no known nonzero gcd and +# no known squares +# """ +# if not eq_dict: return tuple() +# return integralify(to_monic(remove_gcf(subs_squares(eq_dict, known_sq), nonz))) + #################### ### Substitution ### #################### @@ -410,7 +438,8 @@ cpdef dict subs(tuple poly_tup, dict known_powers): cdef tuple temp for exp, coeff in poly_tup: #Get polynomial unit as tuple - temp = ((exp._new(), one),) + # temp = ((exp._new(), one),) + temp = ((exp._new(), coeff.parent().one()),) for var_idx, power in exp.sparse_iter(): if var_idx in known_powers: exp = exp.eadd_p(-power,var_idx) From 614a6394d40e336cf287c57ca47f2176298b0833 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sat, 13 Mar 2021 07:21:12 -0800 Subject: [PATCH 022/414] fixed get_order issue in fusion_ring doctests --- src/sage/combinat/root_system/fusion_ring.py | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 40ac7a376e2..ac3bc7cfa06 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -383,13 +383,11 @@ def test_braid_representation(self, **options): sage: F41 = FusionRing("F4",1) sage: F41.test_braid_representation() - sage: B22 = FusionRing("B2",2) # long time sage: B22.test_braid_representation() # long time (~45s) """ if not self.is_multiplicity_free(): # Braid group representation is not available if self is not multiplicity free return True - print ("test_braid_representation:%s"%self) tester = self._tester(**options) b = self.basis() #Test with different numbers of strands @@ -531,12 +529,12 @@ def get_order(self): EXAMPLES:: - sage: A14 = FusionRing("A1",4) - sage: w = A14.get_order(); w - [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2)] - sage: A14.set_order([w[k] for k in [0,4,1,3,2]]) - sage: [A14(x) for x in A14.get_order()] - [A14(0), A14(4), A14(1), A14(3), A14(2)] + sage: A15 = FusionRing("A1",5) + sage: w = A15.get_order(); w + [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2), (5/2, -5/2)] + sage: A15.set_order([w[k] for k in [0,4,1,3,5,2]]) + sage: [A15(x) for x in A15.get_order()] + [A15(0), A15(4), A15(1), A15(3), A15(5), A15(2)] .. WARNING:: @@ -1069,7 +1067,7 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T Given a simple object in the fusion category, here called ``fusing_anyon`` allowing the universal R-matrix to act on adjacent - pairs in the fusion of ``n_strands`` copies of ``fusing_anyon`` + pairs in the fusion of ``n_strands`` copies of ``fusing_anyon`` produces an action of the braid group. This representation can be decomposed over another anyon, here called ``total_charge_anyon``. See [CHW2015]_. @@ -1092,9 +1090,11 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T EXAMPLES:: sage: A14 = FusionRing("A1",4) + sage: A14.get_order() + [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2)] sage: A14.fusion_labels(["one","two","three","four","five"],inject_variables=True) - sage: A14.basis() - Finite family {(0, 0): one, (1/2, -1/2): two, (1, -1): three, (3/2, -3/2): four, (2, -2): five} + sage: [A14(x) for x in A14.get_order()] + [one, two, three, four, five] sage: two ** 5 5*two + 4*four sage: comp_basis, sig = A14.get_braid_generators(two,two,5,verbose=False) @@ -1102,6 +1102,7 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T True sage: len(comp_basis) == 5 True + """ assert n_strands > 2, "The number of strands must be an integer greater than 2" #Construct associated FMatrix object and solve for F-symbols From 4b7684f671b07e2d0093086a01afc1bcf533b61f Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sun, 14 Mar 2021 16:18:02 -0700 Subject: [PATCH 023/414] docstring revisions --- src/doc/en/reference/references/index.rst | 5 +++++ src/sage/combinat/root_system/fusion_ring.py | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 7753a4e9716..808d2fa07c1 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -917,6 +917,11 @@ REFERENCES: .. [Bond2007] P. Bonderson, Nonabelian anyons and interferometry, Dissertation (2007). https://thesis.library.caltech.edu/2447/ +.. [BDGRTW2019] Bonderson, Delaney, Galindo, Rowell, Tran, and Wang, Zhenghan, + On invariants of modular categories beyond modular data. + J. Pure Appl. Algebra 223 (2019), no. 9, 4065–4088. + :arXiv:`1805.05736`. + .. [BM2004] John M. Boyer and Wendy J. Myrvold, *On the Cutting Edge: *Simplified `O(n)` Planarity by Edge Addition*. Journal of Graph Algorithms and Applications, Vol. 8, No. 3, pp. 241-273, diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index ac3bc7cfa06..96c83378d19 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -951,9 +951,10 @@ def D_minus(self, base_coercion=True): def is_multiplicity_free(self): """ - Return ``True`` if the fusion multiplicities + Return True if the fusion multiplicities :meth:`Nk_ij` are bounded by 1. The :class:`FMatrix` - is available only for multiplicity free :class:`FusionRing`s. + is available only for multiplicity free instances of + :class:`FusionRing`. EXAMPLES:: @@ -1140,13 +1141,20 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T def gens_satisfy_braid_gp_rels(self,sig): """ - Determine if given iterable of n matrices defines a representation of - the Artin braid group on (n+1) strands. Tests correctness of + Return True if the matrices in the list ``sig`` satisfy + the braid relations. This if `n` is the cardinality of ``sig``, this + confirms that these matrices define a representation of + the Artin braid group on `n+1` strands.Tests correctness of get_braid_generators method. EXAMPLES:: - + sage: F41 = FusionRing("F4",1,fusion_labels="f",inject_variables=True) + sage: f1*f1 + f0 + f1 + sage: comp, sig = F41.get_braid_generators(f1,f0,4,verbose=False) + sage: F41.gens_satisfy_braid_gp_rels(sig) + True """ n = len(sig) braid_rels = all(sig[i] * sig[i+1] * sig[i] == sig[i+1] * sig[i] * sig[i+1] for i in range(n-1)) From 14d951b62c45213a4fdc2388ee3e54ffbcd6cfac Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Mon, 15 Mar 2021 15:32:01 -0700 Subject: [PATCH 024/414] revision of get_comp_basis docstring --- src/sage/combinat/root_system/fusion_ring.py | 50 +++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 96c83378d19..5de086252e7 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -982,9 +982,27 @@ def is_multiplicity_free(self): def get_trees(self,top_row,root): """ Recursively enumerate all the admissible trees with given top row and root. - Returns a list of tuples (l1,...,lk) such that - root -> lk # m[-1], lk -> l_{k-1} # m[-2], ..., l1 -> m[0] # m[1], - with top_row = m + + INPUT: + + - ``top_row`` -- a list of basis elements of self + - ``root`` -- a simple element of self + + Let `k` denote the length ``top_row``. This method returns + Returns a list of tuples `(l_1,...,l_{k-2})` such that + + .. MATH:: + + \begin{array}{l} + root \righthookarrow l_{k-2} \otimes m_{k},\\ + l_{k-2} \righthookarrow l_{k-3} \otimes m_{k-1},\\ + \vdots\\ + l_2\righthookarrow l_1\otimes m_3\\ + l_1\righthookarrow m_1\otimes m_2 + \end{array} + + where `a\righthookarrow b\otimes c` means `N_{bc}^a\neq 0`. + """ if len(top_row) == 2: m1, m2 = top_row @@ -995,9 +1013,29 @@ def get_trees(self,top_row,root): def get_comp_basis(self,a,b,n_strands): """ - Get the so-called computational basis for Hom(b, a^n). The basis is a list of - (n-2)-tuples (m_1,...,m_{n//2},l_1,...,l_{(n-3)//2}) such that - each m_i is a monomial in a^2 and l_{j+1} \in l_j # a, and l[-1] \in a # b + Return the so-called computational basis for `\text{Hom}(b, a^n)`. + + INPUT: + + - ``a`` -- a basis element + - ``b`` -- another basis elements + - ``n_strands`` -- the number of strands for a braid group + + Let `n=` ``n_strands`` and let `k` be the greatest integer `\leqslant n/2`. + The braid group acts on ``\text{Hom}(b,a^n)``. This action + is computed in :meth:`get_braid_generators`. This method + returns the computational basis in the form of a set of + fusion trees. Each tree is represented by a `(n-2)`-tuple + + .. MATH:: + + (m_1,\cdots,m_k,l_1,\cdots,l_{k-2}) + + These are computed by :meth:`get_trees`. As a computational + device when ``n_strands`` is odd, we pad the vector `(m_1,\cdots,m_k)` + with an additional `m_{k+1}` equal to `a` before passing it to + :meth:`get_trees`. This `m_{k+1}` does not appear in the output + of this method. """ comp_basis = list() for top in product((a*a).monomials(),repeat=n_strands//2): From 4a4fde802316260971a435dec7290825dcdedbd6 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Tue, 16 Mar 2021 11:46:06 -0700 Subject: [PATCH 025/414] work on get_computational_basis and get_trees --- src/sage/combinat/root_system/fusion_ring.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 5de086252e7..5ba9f6eea0e 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -993,16 +993,15 @@ def get_trees(self,top_row,root): .. MATH:: - \begin{array}{l} - root \righthookarrow l_{k-2} \otimes m_{k},\\ - l_{k-2} \righthookarrow l_{k-3} \otimes m_{k-1},\\ - \vdots\\ - l_2\righthookarrow l_1\otimes m_3\\ - l_1\righthookarrow m_1\otimes m_2 + \\begin{array}{l} + root \\to l_{k-2} \otimes m_{k},\\ + l_{k-2} \\to l_{k-3} \otimes m_{k-1},\\ + \\cdots\\ + l_2\\to l_1\otimes m_3\\ + l_1\\to m_1\otimes m_2 \end{array} - where `a\righthookarrow b\otimes c` means `N_{bc}^a\neq 0`. - + where `a \\to b\otimes c` means `N_{bc}^a\\neq 0`. """ if len(top_row) == 2: m1, m2 = top_row @@ -1011,7 +1010,7 @@ def get_trees(self,top_row,root): m1, m2 = top_row[:2] return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] - def get_comp_basis(self,a,b,n_strands): + def get_computational_basis(self,a,b,n_strands): """ Return the so-called computational basis for `\text{Hom}(b, a^n)`. @@ -1115,7 +1114,8 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T ``comp_basis`` is a list of basis elements of the braid group module, parametrized by a list of fusion ring elements describing a fusion tree. For example with 5 strands the fusion tree - is as follows:: + is as follows. See :meth:`get_computational_basis` and :meth:`get_trees` + for more information. .. image:: ../../../media/fusiontree.png @@ -1157,7 +1157,7 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T #Set up computational basis and compute generators one at a time a, b = fusing_anyon, total_charge_anyon - comp_basis = self.get_comp_basis(a,b,n_strands) + comp_basis = self.get_computational_basis(a,b,n_strands) d = len(comp_basis) if verbose: print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) From 74a2d76a1a5d617f7f24778bb6b243eb248c697d Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Tue, 16 Mar 2021 18:08:48 -0700 Subject: [PATCH 026/414] FusionRing bugfix in weyl_characters: cast level as Integer --- src/sage/combinat/root_system/weyl_characters.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index c254e79a1e8..f77a86efb34 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -18,8 +18,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet from sage.misc.functional import is_even -from sage.rings.all import ZZ - +from sage.rings.all import ZZ, Integer class WeylCharacterRing(CombinatorialFreeModule): r""" @@ -132,6 +131,8 @@ def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conju self._field = None self._basecoer = None self._k = k + if k is not None: + self._k = Integer(k) if ct.is_irreducible(): self._opposition = ct.opposition_automorphism() self._highest = self._space.highest_root() From 7f738aa67d02d2908c18a7be51c85bbd7f94a5bb Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Wed, 17 Mar 2021 16:43:20 -0700 Subject: [PATCH 027/414] no default picking, case-by-case determination of field --- src/sage/combinat/root_system/f_matrix.py | 507 +++++++++++++----- .../fast_parallel_fmats_methods.pyx | 64 ++- .../fast_parallel_fusion_ring_braid_repn.pyx | 8 +- src/sage/combinat/root_system/fusion_ring.py | 26 +- .../combinat/root_system/poly_tup_engine.pxd | 15 +- .../combinat/root_system/poly_tup_engine.pyx | 195 ++++--- 6 files changed, 555 insertions(+), 260 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index fff53f6f00b..08563fade2e 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -11,8 +11,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from functools import cmp_to_key, partial -from itertools import product +from itertools import product, zip_longest import sage.combinat.root_system.fusion_ring as FusionRing import sage.graphs from sage.graphs.generators.basic import EmptyGraph @@ -29,15 +28,17 @@ except: import pickle -from multiprocessing import cpu_count, Pool, set_start_method, TimeoutError +from multiprocessing import cpu_count, Pool, set_start_method import numpy as np +import os from sage.combinat.root_system.fast_parallel_fmats_methods import * from sage.combinat.root_system.poly_tup_engine import * +#Import faster unsafe method (not for client use) +from sage.combinat.root_system.poly_tup_engine import _tup_to_poly from sage.rings.polynomial.polydict import ETuple -from sage.rings.qqbar import AA, QQbar +from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics from sage.rings.real_double import RDF -from itertools import zip_longest class FMatrix(): r"""Return an F-Matrix factory for a FusionRing. @@ -248,16 +249,18 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab #Initialize list of defining equations self.ideal_basis = list() - #Initialize empty set of solved F-symbols - self.solved = set() - - #New attributes of the FMatrix class + #Base field attributes self._field = self._FR.field() r = self._field.defining_polynomial().roots(ring=QQbar,multiplicities=False)[0] self._qqbar_embedding = self._field.hom([r],QQbar) + self._non_cyc_roots = list() + + #Solver state attributes + #Initialize empty set of solved F-symbols + self.solved = set() self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } self._ks = dict() - self._nnz = self.get_known_nonz() + self._nnz = self._get_known_nonz() self._known_vals = dict() self.symbols_known = False @@ -291,7 +294,7 @@ def clear_equations(self): def clear_vars(self): """ - Clear the set of variables. + Clear the set of variables. Also reset the set of solved F-symbols. """ self._FR._basecoer = None self._fvars = { self._var_to_sextuple[key] : key for key in self._var_to_sextuple } @@ -384,9 +387,32 @@ def fmatrix(self,a,b,c,d): return matrix([[self.fmat(a,b,c,d,x,y) for y in Y] for x in X]) def field(self): + r""" + Return the base field containing the F-symbols. When ``self`` is initialized, + the field is set to be the cyclotomic field of the FusionRing associated + to ``self``. The field may change after running :meth:`find_orthogonal_solution`. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("F4",1,conjugate=True)) + sage: f.field() + Cyclotomic Field of order 80 and degree 32 + sage: f.find_orthogonal_solution(verbose=False) + sage: f.field() + Number Field in a with defining polynomial y^64 - 16*y^62 + 104*y^60 - 320*y^58 + 258*y^56 + 1048*y^54 - 2864*y^52 - 3400*y^50 + 47907*y^48 - 157616*y^46 + 301620*y^44 - 322648*y^42 + 2666560*y^40 + 498040*y^38 + 54355076*y^36 - 91585712*y^34 + 592062753*y^32 - 1153363592*y^30 + 3018582788*y^28 - 4848467552*y^26 + 7401027796*y^24 - 8333924904*y^22 + 8436104244*y^20 - 7023494736*y^18 + 4920630467*y^16 - 2712058560*y^14 + 1352566244*y^12 - 483424648*y^10 + 101995598*y^8 - 12532920*y^6 + 1061168*y^4 - 57864*y^2 + 1681 + """ return self._field def FR(self): + r""" + Return the FusionRing associated to ``self``. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D3",1)) + sage: f.FR() + The Fusion Ring of Type D3 and level 1 with Integer Ring coefficients + """ return self._FR def findcases(self,output=False): @@ -493,17 +519,90 @@ def f_to(self,a,b,c,d): [b1, b3, b5] """ - return [y for y in self._FR.basis() if self._FR.Nk_ij(b,c,y) != 0 and self._FR.Nk_ij(a,y,d) != 0] #################### ### Data getters ### #################### - def get_fmats_in_alg_field(self): + def get_fvars(self): + r""" + Return a dictionary of F-symbols. + + The keys are sextuples `(a,b,c,d,x,y)` basis elements of ``self`` and + the values are the corresponding F-symbols `(F^{a,b,c}_d)_{xy}`. + + These values reflect the current state of a solver's computation. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) + creating variables fx1..fx8 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 + sage: f.get_fvars()[(f1, f1, f1, f0, f2, f2)] + fx0 + sage: f.find_orthogonal_solution(verbose=False) + sage: f.get_fvars()[(f1, f1, f1, f0, f2, f2)] + 1 + """ + return self._fvars + + def get_non_cyclotomic_roots(self): + r""" + Return a list of roots that define the extension of the associated + ``FusionRing``'s base ``CyclotomicField`` containing all the F-symbols. + + OUTPUT: + + The list of non-cyclotomic roots is given as a list of elements of + ``self.field()``. + + If ``self.field() == self.FR().field()`` this method returns an empty list. + + When ``self.field()`` is a ``NumberField``, one may use + :meth:``get_qqbar_embedding`` to embed the resulting values into ``QQbar``. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("E6",1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: f.get_non_cyclotomic_roots() + [] + sage: f = FMatrix(FusionRing("E7",2)) # long time + sage: f.find_orthogonal_solution(verbose=False) # long time + sage: f.get_non_cyclotomic_roots() # long time + [-0.7861513777574233?, -0.5558929702514212?] + """ + return sorted(set(self._non_cyc_roots)) + + def get_qqbar_embedding(self): + r""" + Return an embedding from the base field containing F-symbols (the + ``FusionRing``'s ``CyclotomicField``, a ``NumberField``, or ``QQbar``) + into ``QQbar``. + """ + return self._qqbar_embedding + + def get_coerce_map_from_fr_cyclotomic_field(self): + r""" + Return a coercion map from the associated ``FusionRing``'s cyclotomic + field into the base field containing all F-symbols (this could be the + ``FusionRing``'s ``CyclotomicField``, a ``NumberField``, or ``QQbar``). """ - Return F-symbols as elements of the AlgebraicField. This method uses - self._qqbar_embedding to coerce F-symbols into QQbar. + #If base field is different from associated FusionRing's CyclotomicField, + #return coercion map + try: + return self._coerce_map_from_cyc_field + #Otherwise, return identity map CyclotomicField <-> CyclotomicField + except AttributeError: + F = self._FR.field() + return F.hom([F.gen()], F) + + def get_fmats_in_alg_field(self): + r""" + Return F-symbols as elements of the ``AlgebraicField``. This method uses + the embedding defined by :meth:``self.get_qqbar_embedding`` to coerce + F-symbols into QQbar. """ return { sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items() } @@ -513,15 +612,21 @@ def get_radical_expression(self): """ return { sextuple : val.radical_expression() for sextuple, val in get_fmats_in_alg_field().items() } - def get_known_vals(self): - """ - Construct a dictionary of idx, known_val pairs for equation substitution + ####################### + ### Private helpers ### + ####################### + + def _get_known_vals(self): + r""" + Construct a dictionary of ``idx``, ``known_val`` pairs used for substituting + into remaining equations. """ return { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in self.solved } - def get_known_sq(self,eqns=None): - """ - Construct a dictionary of known squares. Keys are variable indices and corresponding values are the squares + def _get_known_sq(self,eqns=None): + r""" + Update ```self``'s dictionary of known squares. Keys are variable + indices and corresponding values are the squares. """ if eqns is None: eqns = self.ideal_basis @@ -529,54 +634,34 @@ def get_known_sq(self,eqns=None): for eq_tup in eqns: if tup_fixes_sq(eq_tup): ks[variables(eq_tup)[0]] = -eq_tup[-1][1] - # return { variables(eq_tup)[0] : -eq_tup[-1][1] for eq_tup in eqns if tup_fixes_sq(eq_tup) } return ks - def get_known_nonz(self): - """ + def _get_known_nonz(self): + r""" Construct an ETuple indicating positions of known nonzero variables. - MUST be called after self._ks = get_known_sq() + + NOTES: + + MUST be called after ``self._ks = _get_known_sq()``. """ nonz = { self._var_to_idx[var] : 100 for var in self._singles } for idx in self._ks: nonz[idx] = 100 return ETuple(nonz, self._poly_ring.ngens()) - def get_qqbar_embedding(self): - """ - Return an embedding from the base field containing F-symbols (the - FusionRing's CyclotomicField, a NumberField, or QQbar) into QQbar - """ - return self._qqbar_embedding + ################################# + ### Useful private predicates ### + ################################# - def get_coerce_map_from_fr_cyclotomic_field(self): + def _is_univariate_in_unknown(self,monom_exp): """ - Return a coercion map from the FusionRing's cyclotomic field into the - base field containing all F-symbols (this could be the FusionRing's - CyclotomicField, a NumberField, or QQbar). - """ - #If base field is different from associated FusionRing's CyclotomicField, - #return coercion map - try: - return self._coerce_map_from_cyc_field - #Otherwise, return identity map CyclotomicField <-> CyclotomicField - except AttributeError: - F = self._FR.field() - return F.hom([F.gen()], F) - - ######################### - ### Useful predicates ### - ######################### - - def is_univariate_in_unknown(self,monom_exp): - """ - Determine if monomial exponent is univariate in a still unknown F-symbol + Determine if monomial exponent is univariate in an unknown F-symbol """ return len(monom_exp.nonzero_values()) == 1 and monom_exp.nonzero_positions()[0] not in self.solved - def is_uni_linear_in_unkwown(self,monom_exp): + def _is_uni_linear_in_unkwown(self,monom_exp): """ - Determine if monomial exponent is univariate and linear in a vstill unknown F-symbol + Determine if monomial exponent is univariate and linear in an unknown F-symbol """ return monom_exp.nonzero_values() == [1] and monom_exp.nonzero_positions()[0] not in self.solved @@ -585,15 +670,46 @@ def is_uni_linear_in_unkwown(self,monom_exp): ############################## def largest_fmat_size(self): - """ - Get the size of the largest F-matrix F^{abc}_d + r""" + Get the size of the largest F-matrix `F^{abc}_d`. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B3",2)) + sage: f.largest_fmat_size() + 4 """ return max(self.fmatrix(*tup).nrows() for tup in product(self._FR.basis(),repeat=4)) - def get_fmats_by_size(self,n): - """ - Partition the F-symbols according to the size of the F-matrix F^{abc}_d - they belong to + def get_fvars_by_size(self,n,indices=False): + r""" + Return the set of F-symbols that are entries of an `n \times n` matrix + `F^{a,b,c}_d`. + + INPUT: + + -``n`` -- positive integer + -``indices`` -- If ``indices`` is ``False`` (default), this method + returns a set of sextuples `(a,b,c,d,x,y)` identifying the + corresponding F-symbol. Each sextuple is a key in the dictionary + returned by :meth:`get_fvars`. + + Otherwise the method returns a list of integer indices that internally + identify the F-symbols. The ``indices=True`` option is meant + for internal use mostly. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("E8",2), inject_variables=True) + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + sage: f.largest_fmat_size() + 2 + sage: f.get_fvars_by_size(2) + {(f2, f2, f2, f2, f0, f0), + (f2, f2, f2, f2, f0, f1), + (f2, f2, f2, f2, f1, f0), + (f2, f2, f2, f2, f1, f1)} """ fvars_copy = deepcopy(self._fvars) solved_copy = deepcopy(self.solved) @@ -603,10 +719,12 @@ def get_fmats_by_size(self,n): F = self.fmatrix(*quadruple) #Discard trivial 1x1 F-matrix, if applicable if F.nrows() == n and F.coefficients() != [1]: - var_set.update(self._var_to_idx[fx] for fx in F.coefficients()) + var_set.update(F.coefficients()) self._fvars = fvars_copy self.solved = solved_copy - return var_set + if indices: + return { self._var_to_idx[fx] for fx in var_set } + return { self._var_to_sextuple[fx] for fx in var_set } ############################ ### Checkpoint utilities ### @@ -614,9 +732,16 @@ def get_fmats_by_size(self,n): def get_fr_str(self): """ - Auto-generate filename string for saving results + Auto-generate identifying key for saving results + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B3",1)) + sage: f.get_fr_str() + 'B31' """ - return self._FR.cartan_type()[0] + str(self._FR.cartan_type()[1]) + str(self._FR.fusion_level()) + ct = self._FR.cartan_type() + return ct.letter + str(ct.n) + str(self._FR.fusion_level()) def save_fvars(self,filename): """ @@ -626,8 +751,12 @@ def save_fvars(self,filename): pickle.dump([self._fvars, self.solved], f) def load_fvars(self,save_dir=""): - """ - If provided, optional param save_dir should have a trailing forward slash + r""" + Load solver state from file. Use this method both for warm-starting + :meth:`find_orthogonal_solution` and to load pickled results. + + If provided, the optional parameter ``save_dir`` must have a trailing + forward slash e.g. "my_dir/" """ with open(save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle", 'rb') as f: self._fvars, self.solved = pickle.load(f) @@ -642,11 +771,13 @@ def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_th return the results (with no duplicates) in a list. This method applies the mapper in parallel if a worker_pool is provided. - # INPUT: - mapper is a string specifying the name of a function defined in the - fast_parallel_fmats_methods module. + INPUT: + + -``mapper`` -- string specifying the name of a function defined in the + ``fast_parallel_fmats_methods`` module. + + NOTES: - ##NOTES: If worker_pool is not provided, function maps and reduces on a single process. If worker_pool is provided, the function attempts to determine whether it should use multiprocessing based on the length of the input iterable. If it can't determine @@ -686,12 +817,19 @@ def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_th ######################## def get_orthogonality_constraints(self,output=True): - """ + r""" Get equations imposed on the F-matrix by orthogonality. - If output=True, equations are returned as polynomial objects. Otherwise, - polynomial generators (stored in the internal tuple representation) are - appended to self.ideal_basis + INPUT: + + -``output``-- a boolean. + + If ``output==True``, orthogonality constraints are returned as + polynomial objects. + + Otherwise, the constraints are appended to ``self.ideal_basis``. + They are stored in the internal tuple representation. The ``output==False`` + option is meant mostly for internal use by the F-matrix solver. """ eqns = list() for tup in product(self._FR.basis(), repeat=4): @@ -702,26 +840,31 @@ def get_orthogonality_constraints(self,output=True): self.ideal_basis.extend([poly_to_tup(eq) for eq in eqns]) def get_defining_equations(self,option,worker_pool=None,output=True): - """ + r""" Get the equations defining the ideal generated by the hexagon or pentagon relations. - Use option='hexagons' to get equations imposed on the F-matrix by the hexagon + Use ``option='hexagons'`` to get equations imposed on the F-matrix by the hexagon relations in the definition of a braided category. - Use option='pentagons' to get equations imposed on the F-matrix by the pentagon + Use ``option='pentagons'`` to get equations imposed on the F-matrix by the pentagon relations in the definition of a monoidal category. - If output=True, equations are returned as polynomial objects. Otherwise, - polynomial generators (stored in the internal tuple representation) are - appended to self.ideal_basis - If a worker_pool is passed, then we use multiprocessing + If ``output=True``, equations are returned as polynomial objects. + + Otherwise, the constraints are appended to ``self.ideal_basis``. + They are stored in the internal tuple representation. The ``output==False`` + option is meant mostly for internal use by the F-matrix solver. + + If a ``worker_pool`` object is passed, then we use multiprocessing. + The ``worker_pool`` object is assumed to be a ``Pool`` object of the + Python ``multiprocessing`` module. """ n_proc = worker_pool._processes if worker_pool is not None else 1 params = [(child_id, n_proc) for child_id in range(n_proc)] eqns = self.map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) if output: - return [self.tup_to_fpoly(p) for p in eqns] + return [self._tup_to_fpoly(p) for p in eqns] self.ideal_basis.extend(eqns) ############################ @@ -734,7 +877,14 @@ def tup_to_fpoly(self,eq_tup): """ return tup_to_poly(eq_tup,parent=self._poly_ring) - def solve_for_linear_terms(self,eqns=None): + def _tup_to_fpoly(self,eq_tup): + r""" + Faster version of :meth:`tup_to_fpoly`. Unsafe for client use, since it + avoids implicit casting and it may lead to segmentation faults. + """ + return _tup_to_poly(eq_tup,parent=self._poly_ring) + + def _solve_for_linear_terms(self,eqns=None): """ Solve for a linear term occurring in a two-term equation. """ @@ -745,7 +895,7 @@ def solve_for_linear_terms(self,eqns=None): for eq_tup in eqns: if len(eq_tup) == 1: m = eq_tup[0][0] - if self.is_univariate_in_unknown(m): + if self._is_univariate_in_unknown(m): var = m.nonzero_positions()[0] self._fvars[self._var_to_sextuple[self._poly_ring.gen(var)]] = tuple() self.solved.add(var) @@ -755,7 +905,7 @@ def solve_for_linear_terms(self,eqns=None): max_var = monomials[0].emax(monomials[1]).nonzero_positions()[0] for this, m in enumerate(monomials): other = (this+1)%2 - if self.is_uni_linear_in_unkwown(m) and m.nonzero_positions()[0] == max_var and monomials[other][m.nonzero_positions()[0]] == 0: + if self._is_uni_linear_in_unkwown(m) and m.nonzero_positions()[0] == max_var and monomials[other][m.nonzero_positions()[0]] == 0: var = m.nonzero_positions()[0] rhs_key = monomials[other] rhs_coeff = -eq_tup[other][1] / eq_tup[this][1] @@ -764,17 +914,18 @@ def solve_for_linear_terms(self,eqns=None): linear_terms_exist = True return linear_terms_exist - def backward_subs(self): + def _backward_subs(self): """ Backward substitution step. Traverse variables in reverse lexicographical order. """ + one = self._field.one() for var in reversed(self._poly_ring.gens()): sextuple = self._var_to_sextuple[var] rhs = self._fvars[sextuple] d = { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in variables(rhs) if var_idx in self.solved } if d: - kp = compute_known_powers(get_variables_degrees([rhs]), d) - self._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp),self._ks).items()) + kp = compute_known_powers(get_variables_degrees([rhs]), d, one) + self._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one),self._ks).items()) def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): """ @@ -782,9 +933,9 @@ def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update """ if eqns is None: eqns = self.ideal_basis - self._ks, self._var_degs = self.get_known_sq(eqns), get_variables_degrees(eqns) - self._nnz = self.get_known_nonz() - self._kp = compute_known_powers(self._var_degs,self.get_known_vals()) + self._ks, self._var_degs = self._get_known_sq(eqns), get_variables_degrees(eqns) + self._nnz = self._get_known_nonz() + self._kp = compute_known_powers(self._var_degs,self._get_known_vals(), self._field.one()) if worker_pool is not None and children_need_update: #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes @@ -802,15 +953,16 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T ret = False if required_vars is None: required_vars = self._poly_ring.gens() - poly_sortkey = cmp_to_key(poly_tup_cmp) + # poly_sortkey = cmp_to_key(poly_tup_cmp) + poly_sortkey = poly_tup_sortkey_degrevlex - #Testing new _fvars representation... Unzip polynomials + #Unzip polynomials self._fvars = { sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items() } while True: - linear_terms_exist = self.solve_for_linear_terms(eqns) + linear_terms_exist = self._solve_for_linear_terms(eqns) if not linear_terms_exist: break - self.backward_subs() + self._backward_subs() #Support early termination in case only some F-symbols are needed req_vars_known = all(self._fvars[self._var_to_sextuple[var]] in self._FR.field() for var in required_vars) @@ -818,12 +970,13 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T #Compute new reduction params, send to child processes if any, and update eqns self.update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) - eqns = sorted(self.map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool), key=poly_sortkey) + eqns = self.map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool) + eqns.sort(key=poly_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) #Zip up _fvars before exiting - self._fvars = { sextuple : self.tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items() } + self._fvars = { sextuple : self._tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items() } if ret: return eqns self.ideal_basis = eqns @@ -890,7 +1043,7 @@ def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose= # nmax = self.largest_fmat_size() # vars_by_size = list() # for i in range(nmax+1): - # vars_by_size.append(self.get_fmats_by_size(i)) + # vars_by_size.append(self.get_fvars_by_size(i)) for comp, comp_eqns in self.partition_eqns(graph,verbose=verbose).items(): #Check if component is too large to process @@ -931,6 +1084,42 @@ def get_component_variety(self,var,eqns): ### Solution method ### ####################### + def attempt_number_field_computation(self): + """ + Based on the ``CartanType`` of ``self``, determine whether to attempt + to find a ``NumberField`` containing all the F-symbols based on data + known on March 17, 2021. + + For certain ``FusionRing``s, the number field computation does not + seem to terminate. In these cases, we report F-symbols as elements + of the ``AlgebraicField``. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("E6",2)) + sage: f.attempt_number_field_computation() + False + sage: f = FMatrix(FusionRing("G2",1)) + sage: f.attempt_number_field_computation() + True + """ + ct = self._FR.cartan_type() + k = self._FR._k + if ct.letter == 'A': + if ct.n == 1 and k >= 9: + return False + if ct.letter == 'C': + if ct.n >= 9 and k == 1: + return False + if ct.letter == 'E': + if ct.n < 8 and k == 2: + return False + if ct.letter == 'F' and k == 2: + return False + if ct.letter == 'G' and k == 2: + return False + return True + def get_explicit_solution(self,eqns=None,verbose=True): """ When this method is called, the solution is already found in @@ -973,16 +1162,21 @@ def get_explicit_solution(self,eqns=None,verbose=True): if must_change_base_field: #Attempt to compute smallest number field containing all the F-symbols #If calculation takes too long, we use QQbar as the base field - proc = Pool(1) - input_args = ((('get_appropriate_number_field',id(self)),non_cyclotomic_roots),) - p = proc.apply_async(executor,input_args) - try: - self._field, bf_elts, self._qqbar_embedding = p.get(timeout=100) - except TimeoutError: + if self.attempt_number_field_computation(): + roots = [self._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] + self._field, bf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots,minimal=True) + else: + # proc = Pool(1) + # input_args = ((('get_appropriate_number_field',id(self)),non_cyclotomic_roots),) + # p = proc.apply_async(executor,input_args) + # try: + # self._field, bf_elts, self._qqbar_embedding = p.get(timeout=100) + # except TimeoutError: self._field = QQbar bf_elts = [self._qqbar_embedding(F.gen())] bf_elts += [rhs for fx,rhs in non_cyclotomic_roots] self._qqbar_embedding = lambda x : x + self._non_cyc_roots = bf_elts[1:] #Embed cyclotomic field into newly constructed base field cyc_gen_as_bf_elt = bf_elts.pop(0) @@ -1008,15 +1202,58 @@ def get_explicit_solution(self,eqns=None,verbose=True): self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } for fx, rhs in numeric_fvars.items(): self._fvars[self._var_to_sextuple[self._poly_ring.gen(fx)]] = ((ETuple({},nvars),rhs),) - self.backward_subs() + self._backward_subs() self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } self.clear_equations() - def find_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): - """ - Solver - If provided, optional param save_dir (for saving computed _fvars) should have a trailing forward slash - Supports "warm" start. Use load_fvars to re-start computation from checkpoint + #Update base field attributes + self._FR._field = self.field() + self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() + for x in self._FR.basis(): + x.q_dimension.clear_cache() + + def find_orthogonal_solution(self,checkpoint=False,save_results=False,save_dir="",verbose=True,use_mp=True): + r""" + Find an orthogonal solution to the pentagon equations associated to the + monoidal category represented by ``self``. + + INPUT: + + -``checkpoint`` -- (optional) a boolean indicating whether the computation should + be checkpointed. Depending on the associated ``CartanType``, the computation + may take hours to complete. For large examples, checkpoints are + recommended. This method supports "warm" starting, so the calculation + may be resumed from a checkpoint. Checkpoints store necessary state in + the form of pickle files. + + -``save_results`` -- (optional) a boolean indicating whether the F-symbols + should be stored to file as a pickle for later use. + + -``save_dir`` -- (optional) a string specifying the directory in which + to save the pickled F-symbols. If used, the string must have a trailing + forward slash e.g. "my_dir/" + The file name is "saved_fvars_" + ``key`` + .pickle, where + key is computed by :meth:`get_fr_str`. The file name is fixed to allow + for automatic loading. The ``save_dir`` is also used to specify the + location of a checkpoint pickle, if one exists. + + -``use_mp`` -- (optional) a boolean indicating whether to use + multiprocessing to speed up calculation. The default value + ``True`` is recommended, since parallel processing yields results + much quicker. + + OUTPUT: + + This method returns ``None``. If the solver ran successfully, the + results may be accessed through various methods, such as + :meth:``get_fvars``, :meth:``fmatrix``, :meth:``fmat``, etc. + + In many cases the F-symbols obtained are in fact real. In any case, the + F-symbols are obtained as elements of the associated ``FusionRing``'s + ``CyclotomicField``, a computed ``NumberField``, or ``QQbar``. + Currently, the output field is determined based on the ``CartanType`` + associated to ``self``. See :meth:``attempt_number_field_computation`` + for details. """ #Set multiprocessing parameters. Context can only be set once, so we try to set it self.clear_equations() @@ -1029,22 +1266,29 @@ def find_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) + #Attempt warm-start + try: + self.load_fvars(save_dir) + except FileNotFoundError: + pass + #Set up hexagon equations and orthogonality constraints - poly_sortkey = cmp_to_key(poly_tup_cmp) + poly_sortkey = poly_tup_sortkey_degrevlex self.get_orthogonality_constraints(output=False) self.get_defining_equations('hexagons',worker_pool=pool,output=False) if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) #Set up equations graph. Find GB for each component in parallel. Eliminate variables - self.ideal_basis = sorted(self.par_graph_gb(worker_pool=pool,verbose=verbose), key=poly_sortkey) + self.ideal_basis = self.par_graph_gb(worker_pool=pool,verbose=verbose) + self.ideal_basis.sort(key=poly_sortkey) self.triangular_elim(worker_pool=pool,verbose=verbose) #Report progress and checkpoint! if verbose: print("Hex elim step solved for {} / {} variables".format(len(self.solved), len(self._poly_ring.gens()))) filename = save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle" - self.save_fvars(filename) + if checkpoint: self.save_fvars(filename) #Update reduction parameters, also in children if any self.update_reduction_params(worker_pool=pool,children_need_update=True) @@ -1053,27 +1297,30 @@ def find_orthogonal_solution(self,use_mp=True,save_dir="",verbose=True): self.get_defining_equations('pentagons',worker_pool=pool,output=False) if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) - self.ideal_basis = sorted(self.ideal_basis, key=poly_sortkey) + self.ideal_basis.sort(key=poly_sortkey) self.triangular_elim(worker_pool=pool,verbose=verbose) #Report progress and checkpoint! if verbose: print("Pent elim step solved for {} / {} variables".format(len(self.solved), len(self._poly_ring.gens()))) - self.save_fvars(filename) + if checkpoint: self.save_fvars(filename) #Close worker pool to free resources if pool is not None: pool.close() #Set up new equations graph and compute variety for each component - self.ideal_basis = sorted(self.par_graph_gb(term_order="lex",verbose=verbose), key=poly_sortkey) + self.ideal_basis = self.par_graph_gb(term_order="lex",verbose=verbose) + self.ideal_basis.sort(key=poly_sortkey) self.triangular_elim(verbose=verbose) + if checkpoint: self.save_fvars(filename) self.get_explicit_solution(verbose=verbose) - self.save_fvars(filename) + + #The calculation was successful, so we may delete checkpoints self.symbols_known = True - self._FR._field = self.field() - self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() - for x in self._FR.basis(): - x.q_dimension.clear_cache() + if checkpoint: + os.remove(filename) + if save_results: + self.save_fvars(filename) ######################### ### Cyclotomic method ### @@ -1114,29 +1361,12 @@ def substitute_degree_one(self, eqns=None): new_knowns.add(eq.lm()) useless.add(eq) - #Solve equation of the form x_i x_j + k == 0 for x_i - # print("equation: ", eq, "variables ", eq.variables()) - # if eq.degree() == 2 and max(eq.degrees()) == 1 and len(eq.variables()) == 2 and eq.variable(0) not in self.solved: - # self._fvars[self._var_to_sextuple[str(eq.variable(0))]] = - eq.constant_coefficient() / eq.variable(1) - # print("Subbed {} for {}".format(- eq.constant_coefficient() / eq.variable(1), eq.variable(0))) - # #Add variable to set of known values and remove this equation - # new_knowns.add(eq.variable(0)) - # useless.add(eq) - #Update fvars depending on other variables self.solved.update(new_knowns) for sextuple, rhs in self._fvars.items(): d = { var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self.solved } - if len(d) == 2: print("THREE TERM LINEAR EQUATION ENCOUNTERED!") if d: self._fvars[sextuple] = rhs.subs(d) - - # if rhs.variables() and rhs.variable() in self.solved: - # assert rhs.is_univariate(), "RHS expression is not univariate" - # d = { rhs.variable() : } - # # print("Performing substitution of {} with dictionary {}".format(rhs, d)) - # self._fvars[sextuple] = rhs.subs(d) - return new_knowns, useless def update_equations(self): @@ -1225,7 +1455,6 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o ### Verifications ### ##################### - def verify_hexagons(self): """ Ensure the hexagon equations are satisfied diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 3d0084c50ab..7e9c5ba980a 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -17,7 +17,7 @@ from sage.combinat.root_system.poly_tup_engine cimport * from sage.rings.ideal import Ideal from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.qqbar import number_field_elements_from_algebraics +# from sage.rings.qqbar import number_field_elements_from_algebraics #Define a global temporary worker results repository cdef list worker_results = list() @@ -89,41 +89,41 @@ def executor(params): ### Mappers ### ############### -cdef _fmat(fvars, _Nk_ij, one, a, b, c, d, x, y): +cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): """ Cython version of fmat class method. Using cdef for fastest dispatch """ if _Nk_ij(a,b,x) == 0 or _Nk_ij(x,c,d) == 0 or _Nk_ij(b,c,y) == 0 or _Nk_ij(a,y,d) == 0: return 0 #Some known F-symbols - if a == one: + if a == id_anyon: if x == b and y == d: return 1 else: return 0 - if b == one: + if b == id_anyon: if x == a and y == c: return 1 else: return 0 - if c == one: + if c == id_anyon: if x == d and y == b: return 1 else: return 0 return fvars[a,b,c,d,x,y] -cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, one, tuple sextuple): +cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): """ Given an FMatrix factory and a sextuple, return a hexagon equation as a polynomial object """ a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = r_matrix(a,c,e)*_fmat(fvars,Nk_ij,one,a,c,b,d,e,g)*r_matrix(b,c,g) + lhs = r_matrix(a,c,e)*_fmat(fvars,Nk_ij,id_anyon,a,c,b,d,e,g)*r_matrix(b,c,g) rhs = 0 for f in basis: - rhs += _fmat(fvars,Nk_ij,one,c,a,b,d,e,f)*r_matrix(f,c,d)*_fmat(fvars,Nk_ij,one,a,b,c,d,f,g) + rhs += _fmat(fvars,Nk_ij,id_anyon,c,a,b,d,e,f)*r_matrix(f,c,d)*_fmat(fvars,Nk_ij,id_anyon,a,b,c,d,f,g) return lhs-rhs @cython.wraparound(False) @@ -138,35 +138,37 @@ cpdef get_reduced_hexagons(factory, tuple mp_params): cdef child_id, n_proc cdef unsigned long i child_id, n_proc = mp_params - cdef tuple sextuple + cdef tuple sextuple, red #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) cdef dict fvars = factory._fvars r_matrix = factory._FR.r_matrix _Nk_ij = factory._FR.Nk_ij - one = factory._FR.one() + id_anyon = factory._FR.one() + cdef NumberFieldElement_absolute one = factory._field.one() cdef ETuple _nnz = factory._nnz cdef dict _ks = factory._ks #Computation loop for i, sextuple in enumerate(product(basis,repeat=6)): if i % n_proc == child_id: - he = req_cy(basis,r_matrix,fvars,_Nk_ij,one,sextuple) + he = req_cy(basis,r_matrix,fvars,_Nk_ij,id_anyon,sextuple) if he: - worker_results.append(reduce_poly_dict(he.dict(),_nnz,_ks)) + red = reduce_poly_dict(he.dict(),_nnz,_ks,one) + worker_results.append(red) -cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, one, zero, tuple nonuple, bint prune=False): +cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, id_anyon, zero, tuple nonuple, bint prune=False): """ Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object """ a, b, c, d, e, f, g, k, l = nonuple - cdef lhs = _fmat(fvars,Nk_ij,one,f,c,d,e,g,l)*_fmat(fvars,Nk_ij,one,a,b,l,e,f,k) + cdef lhs = _fmat(fvars,Nk_ij,id_anyon,f,c,d,e,g,l)*_fmat(fvars,Nk_ij,id_anyon,a,b,l,e,f,k) if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information return zero cdef rhs = zero for h in basis: - rhs += _fmat(fvars,Nk_ij,one,a,b,c,g,f,h)*_fmat(fvars,Nk_ij,one,a,h,d,e,g,k)*_fmat(fvars,Nk_ij,one,b,c,d,k,h,l) + rhs += _fmat(fvars,Nk_ij,id_anyon,a,b,c,g,f,h)*_fmat(fvars,Nk_ij,id_anyon,a,h,d,e,g,k)*_fmat(fvars,Nk_ij,id_anyon,b,c,d,k,h,l) return lhs - rhs @cython.wraparound(False) @@ -189,7 +191,8 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): cdef dict fvars = factory._fvars r_matrix = factory._FR.r_matrix _Nk_ij = factory._FR.Nk_ij - one = factory._FR.one() + id_anyon = factory._FR.one() + cdef NumberFieldElement_absolute one = factory._field.one() cdef ETuple _nnz = factory._nnz cdef dict _ks = factory._ks cdef MPolynomial_libsingular zero = factory._poly_ring.zero() @@ -197,9 +200,9 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): #Computation loop for i, nonuple in enumerate(product(basis,repeat=9)): if i % n_proc == child_id: - pe = feq_cy(basis,fvars,_Nk_ij,one,zero,nonuple,prune=prune) + pe = feq_cy(basis,fvars,_Nk_ij,id_anyon,zero,nonuple,prune=prune) if pe: - red = reduce_poly_dict(pe.dict(),_nnz,_ks) + red = reduce_poly_dict(pe.dict(),_nnz,_ks,one) worker_results.append(red) cpdef update_reduce(factory, tuple eq_tup): @@ -207,8 +210,9 @@ cpdef update_reduce(factory, tuple eq_tup): Substitute known values, known squares, and reduce! """ global worker_results - cdef dict eq_dict = subs(eq_tup,factory._kp) - cdef tuple red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks) + cdef NumberFieldElement_absolute one =factory._field.one() + cdef dict eq_dict = subs(eq_tup,factory._kp,one) + cdef tuple red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks,one) worker_results.append(red) cpdef compute_gb(factory, tuple args): @@ -243,20 +247,22 @@ cpdef compute_gb(factory, tuple args): cpdef update_child_fmats(factory, tuple data_tup): """ - One-to-all communication used to update fvars after triangular elim step. + One-to-all communication used to update FMatrix object after each triangular + elim step. We must update the algorithm's state values. These are: + _fvars, solved, _ks, _var_degs, _nnz, and _kp. """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object factory._fvars, factory.solved, factory._ks, factory._var_degs = data_tup - factory._nnz = factory.get_known_nonz() - factory._kp = compute_known_powers(factory._var_degs,factory.get_known_vals()) + factory._nnz = factory._get_known_nonz() + factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) -def get_appropriate_number_field(factory,non_cyclotomic_roots): - """ - If needed, find a NumberField containing the roots given roots - """ - roots = [factory._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] - return number_field_elements_from_algebraics(roots,minimal=True) +# def get_appropriate_number_field(factory,non_cyclotomic_roots): +# """ +# If needed, find a NumberField containing the roots given roots +# """ +# roots = [factory._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] +# return number_field_elements_from_algebraics(roots,minimal=True) ################ ### Reducers ### diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 01016ca3e0d..a55bc74b83a 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -77,7 +77,7 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) sage: one.weight() (1/2, -1/2) - sage: FR.get_comp_basis(one,two,4) + sage: FR.get_computational_basis(one,two,4) [(two, two), (two, idd), (idd, two)] sage: mid_sig_ij(FR, (two, two), (two, idd), one, two) (zeta48^10 - zeta48^2)*fx0*fx1*fx8 + (zeta48^2)*fx2*fx3*fx8 @@ -117,7 +117,7 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) sage: X.weight() (1/2, 1/2) - sage: FR.get_comp_basis(X,X,3) + sage: FR.get_computational_basis(X,X,3) [(Y2,), (Y1,), (I0,)] sage: odd_one_out_ij(FR,Y2,Y1,X,X) (zeta40^10)*fx205*fx208 + (zeta40^14 - zeta40^10 + zeta40^6 - zeta40^2)*fx206*fx209 + (zeta40^2)*fx207*fx210 @@ -157,7 +157,7 @@ cpdef sig_2k(fusion_ring, tuple args): cdef int ctr = -1 global worker_results #Get computational basis - cdef list comp_basis = fusion_ring.get_comp_basis(a,b,n_strands) + cdef list comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) cdef dict basis_dict = { elt : i for i, elt in enumerate(comp_basis) } cdef int dim = len(comp_basis) cdef set entries = set() @@ -220,7 +220,7 @@ cpdef odd_one_out(fusion_ring, tuple args): a, b, n_strands = fn_args cdef ctr = -1 #Get computational basis - comp_basis = fusion_ring.get_comp_basis(a,b,n_strands) + comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) basis_dict = { elt : i for i, elt in enumerate(comp_basis) } dim = len(comp_basis) for i in range(dim): diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 5ba9f6eea0e..4031c3e10ea 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -1012,7 +1012,7 @@ def get_trees(self,top_row,root): def get_computational_basis(self,a,b,n_strands): """ - Return the so-called computational basis for `\text{Hom}(b, a^n)`. + Return the so-called computational basis for `\\text{Hom}(b, a^n)`. INPUT: @@ -1021,7 +1021,7 @@ def get_computational_basis(self,a,b,n_strands): - ``n_strands`` -- the number of strands for a braid group Let `n=` ``n_strands`` and let `k` be the greatest integer `\leqslant n/2`. - The braid group acts on ``\text{Hom}(b,a^n)``. This action + The braid group acts on `\\text{Hom}(b,a^n)`. This action is computed in :meth:`get_braid_generators`. This method returns the computational basis in the form of a set of fusion trees. Each tree is represented by a `(n-2)`-tuple @@ -1031,7 +1031,7 @@ def get_computational_basis(self,a,b,n_strands): (m_1,\cdots,m_k,l_1,\cdots,l_{k-2}) These are computed by :meth:`get_trees`. As a computational - device when ``n_strands`` is odd, we pad the vector `(m_1,\cdots,m_k)` + device when ``n_strands`` is odd, we pad the vector `(m_1,\cdots,m_k)` with an additional `m_{k+1}` equal to `a` before passing it to :meth:`get_trees`. This `m_{k+1}` does not appear in the output of this method. @@ -1052,17 +1052,17 @@ def fmats(self): """ return FMatrix.FMatrix(self) - def emap(self,mapper,input_args,worker_pool=None): + def _emap(self,mapper,input_args,worker_pool=None): """ Apply the given mapper to each element of the given input iterable and return the results (with no duplicates) in a list. This method applies the mapper in parallel if a worker_pool is provided. - # INPUT: - mapper is a string specifying the name of a function defined in the - fast_parallel_fmats_methods module. + INPUT: - input_args should be a tuple holding arguments to be passed to mapper + - ``mapper`` -- a string specifying the name of a function defined in the + fast_parallel_fmats_methods module. + - ``input_args`` -- a tuple holding arguments to be passed to mapper ##NOTES: If worker_pool is not provided, function maps and reduces on a single process. @@ -1095,9 +1095,9 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T """ INPUT: - Compute generators of the Artin braid group on n_strands strands. If + Compute generators of the Artin braid group on `n=` n_strands strands. If fusing_anyon = a and total_charge_anyon = b, the generators are - endomorphisms of Hom(a^n_strands, b) + endomorphisms of `\\text{Hom}(b, a^n)` - ``fusing_anyon`` -- a basis element of self - ``total_charge_anyon`` -- a basis element of self @@ -1143,7 +1143,7 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T True """ - assert n_strands > 2, "The number of strands must be an integer greater than 2" + assert int(n_strands) > 2, "The number of strands must be an integer greater than 2" #Construct associated FMatrix object and solve for F-symbols if not self.fmats.symbols_known: self.fmats.find_orthogonal_solution(verbose=verbose) @@ -1167,12 +1167,12 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T #Compute even-indexed generators using F-matrices for k in range(1,n_strands//2): - entries = self.emap('sig_2k',(k,a,b,n_strands),pool) + entries = self._emap('sig_2k',(k,a,b,n_strands),pool) gens[2*k] = matrix(dict(entries)) #If n_strands is odd, we compute the final generator if n_strands % 2: - entries = self.emap('odd_one_out',(a,b,n_strands),pool) + entries = self._emap('odd_one_out',(a,b,n_strands),pool) gens[n_strands-1] = matrix(dict(entries)) return comp_basis, [gens[k] for k in sorted(gens)] diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 5d5e49263d3..5f614ae69de 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -1,6 +1,6 @@ from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular - +from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute cpdef tuple poly_to_tup(MPolynomial_libsingular poly) cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) @@ -11,11 +11,10 @@ cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cpdef bint tup_fixes_sq(tuple eq_tup) cpdef dict subs_squares(dict eq_dict, dict known_sq) -cdef tuple to_monic(dict eq_dict) -cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq) -cpdef dict compute_known_powers(ETuple max_deg, dict val_dict) -cpdef dict subs(tuple poly_tup, dict known_powers) -cpdef int poly_tup_cmp(tuple tleft, tuple tright) +cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one) +cpdef dict subs(tuple poly_tup, dict known_powers, one) +# cpdef int poly_tup_cmp(tuple tleft, tuple tright) +cpdef tuple poly_tup_sortkey_degrevlex(tuple eq_tup) -cdef tuple tup_mul(tuple p1, tuple p2) -cdef dict remove_gcf(dict eq_dict, ETuple nonz) +cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) +cpdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 8499745abeb..45b4f34679c 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -9,15 +9,11 @@ Arithmetic Engine for polynomials as tuples # **************************************************************************** from functools import cmp_to_key -from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular -from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.term_order import TermOrder -from sage.rings.rational_field import QQ -from sage.arith.functions cimport LCM_list +# from sage.arith.functions cimport LCM_list #Pre-compute common values for speed -one = QQ.one() degrevlex_sortkey = TermOrder().sortkey_degrevlex ########### @@ -75,9 +71,21 @@ cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingu # Maybe the following is faster but we need to ensure all coefficients are # already in fmats._poly_ring.base_ring() so that implicit casting is avoided # (this is pretty slow) - # return parent._element_constructor_({ exp : c for exp, c in tup_of_pairs }, check=False) + # return parent._element_constructor_(dict(eq_tup), check=False) return parent(dict(eq_tup)) +cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent): + r""" + Faster version of :meth:`tup_to_poly`. Unsafe for client use, since it + avoids implicit casting and it may lead to segmentation faults. + + Safe for internal use because our methods ensure that + polynomial coefficients always lie in the same base ring. + """ + return parent._element_constructor_(dict(eq_tup), check=False) + + + ###################### ### "Change rings" ### ###################### @@ -210,6 +218,7 @@ cpdef constant_coeff(tuple eq_tup): sage: constant_coeff(poly_to_tup(x**5 + x*y*z - 9)) -9 """ + cdef ETuple exp for exp, coeff in eq_tup: if exp.is_constant(): return coeff @@ -228,6 +237,7 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): sage: tup_to_poly(apply_coeff_map(poly_to_tup(x + 2*y + 3*z), sq), parent=R) x + 4*y + 9*z """ + cdef ETuple exp cdef list new_tup = list() for exp, coeff in eq_tup: new_tup.append((exp, coeff_map(coeff))) @@ -314,7 +324,7 @@ cdef dict remove_gcf(dict eq_dict, ETuple nonz): ret[exp.esub(common_powers)] = c return ret -cdef tuple to_monic(dict eq_dict): +cdef tuple to_monic(dict eq_dict, one): """ Return tuple of pairs (ETuple, coeff) describing the monic polynomial associated to eq_dict Here, the leading coefficient is chosen according to the degree reverse lexicographic ordering @@ -325,21 +335,22 @@ cdef tuple to_monic(dict eq_dict): cdef ETuple lm = ord_monoms[-1] lc = eq_dict[lm] if not lc: return tuple() - # cdef list ret = [(lm, one)] - cdef list ret = [(lm, lc.parent().one())] + cdef list ret = [(lm, one)] inv_lc = lc.inverse_of_unit() cdef ETuple exp for exp in reversed(ord_monoms[:-1]): ret.append((exp, inv_lc * eq_dict[exp])) return tuple(ret) -cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): +cpdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one): """ Return a dictionary describing a monic polynomial with no known nonzero gcd and no known squares """ if not eq_dict: return tuple() - return to_monic(remove_gcf(subs_squares(eq_dict, known_sq), nonz)) + cdef dict sq_rmvd = subs_squares(eq_dict, known_sq) + cdef dict gcf_rmvd = remove_gcf(sq_rmvd, nonz) + return to_monic(gcf_rmvd, one) # cdef int common_denom(tuple eq_tup): # #Compute the common denominator @@ -370,7 +381,7 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): ### Substitution ### #################### -cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): +cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): """ Pre-compute powers of known values for efficiency when preparing to substitute into a list of polynomials. @@ -390,7 +401,7 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) - sage: compute_known_powers(max_deg, known_val) + sage: compute_known_powers(max_deg, known_val, R.base_ring().one()) {0: [(((0, 0, 0), 1),), (((0, 0, 0), -1),), (((0, 0, 0), 1),), @@ -413,7 +424,7 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict): known_powers[var_idx][power+1] = tup_mul(known_powers[var_idx][power],val_dict[var_idx]) return known_powers -cpdef dict subs(tuple poly_tup, dict known_powers): +cpdef dict subs(tuple poly_tup, dict known_powers, one): """ Substitute given variables into a polynomial tuple @@ -426,10 +437,11 @@ cpdef dict subs(tuple poly_tup, dict known_powers): sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) sage: poly_tup = poly_to_tup(polys[0]) - sage: subs(poly_tup, compute_known_powers(max_deg, known_val)) + sage: one = R.base_ring().one() + sage: subs(poly_tup, compute_known_powers(max_deg, known_val, one), one) {(0, 0, 0): 0} sage: poly_tup = poly_to_tup(polys[1]) - sage: subs(poly_tup, compute_known_powers(max_deg, known_val)) + sage: subs(poly_tup, compute_known_powers(max_deg, known_val, one), one) {(0, 1, 0): 1, (0, 6, 0): 1} """ cdef dict subbed = {} @@ -438,8 +450,7 @@ cpdef dict subs(tuple poly_tup, dict known_powers): cdef tuple temp for exp, coeff in poly_tup: #Get polynomial unit as tuple - # temp = ((exp._new(), one),) - temp = ((exp._new(), coeff.parent().one()),) + temp = ((exp._new(), one),) for var_idx, power in exp.sparse_iter(): if var_idx in known_powers: exp = exp.eadd_p(-power,var_idx) @@ -473,54 +484,104 @@ cdef tuple tup_mul(tuple p1, tuple p2): #Implement richcmp comparator object that can be passed in as key to sorted method -cpdef int poly_tup_cmp(tuple tleft, tuple tright): - """ - Determine which polynomial is larger with respect to the degrevlex ordering - - EXAMPLES:: - - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp - sage: R. = PolynomialRing(QQ) - sage: p1 = x*y*z - x**2 + 3/2 - sage: p2 = x*y*z - x * y +1/2 - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) - True - sage: R. = PolynomialRing(CyclotomicField(20)) - sage: zeta20 = R.base_ring().gen() - sage: p1 = zeta20**2 * x*z**2 - 2*zeta20 - sage: p2 = y**3 + 1/4 - sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) - True - - TESTS: +# cpdef int poly_tup_cmp(tuple tleft, tuple tright): +# """ +# Determine which polynomial is larger with respect to the degrevlex ordering +# +# EXAMPLES:: +# +# sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp +# sage: R. = PolynomialRing(QQ) +# sage: p1 = x*y*z - x**2 + 3/2 +# sage: p2 = x*y*z - x * y +1/2 +# sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup +# sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) +# True +# sage: R. = PolynomialRing(CyclotomicField(20)) +# sage: zeta20 = R.base_ring().gen() +# sage: p1 = zeta20**2 * x*z**2 - 2*zeta20 +# sage: p2 = y**3 + 1/4 +# sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) +# True +# +# TESTS: +# +# sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp, poly_to_tup +# sage: R. = PolynomialRing(CyclotomicField(20)) +# sage: p1 = R.random_element() +# sage: p2 = R.random_element() +# sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) +# True +# sage: (p1 > p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) > 0) +# True +# sage: poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p1)) == 0 +# True +# """ +# cdef int i, ret, sf, sg, val +# cdef ETuple f, g +# ret = 0 +# for i in range(min(len(tleft),len(tright))): +# f, g = tleft[i][0], tright[i][0] +# if f == g: +# if tleft[i][1] != tright[i][1]: +# ret = -1 + 2*(tleft[i][1] > tright[i][1]) +# else: +# sf, sg = 0, 0 +# for val in f.nonzero_values(sort=False): +# sf += val +# for val in g.nonzero_values(sort=False): +# sg += val +# ret = -1 + 2*(sf > sg or ( sf == sg and f.reversed() < g.reversed() )) +# if ret != 0: +# return ret +# return len(tleft) - len(tright) + +import numpy as np +cpdef tuple poly_tup_sortkey_degrevlex(tuple eq_tup): + """ + Return the sortkey of a polynomial represented as a tuple of (ETuple, coeff) + pairs with respect to the degree reversed lexicographical term order. + + Using this key to sort polynomial tuples results in comparing polynomials + term by term (we assume the tuple representation is sorted so that the + leading term with respect to the degree reverse lexicographical order comes + first). For each term, we first compare degrees, then the monomials themselves, + then the corresponding coefficient. + + This method relies on the built-in comparison of ETuple's. + + EXAMPLES :: - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp, poly_to_tup - sage: R. = PolynomialRing(CyclotomicField(20)) - sage: p1 = R.random_element() - sage: p2 = R.random_element() - sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) - True - sage: (p1 > p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) > 0) - True - sage: poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p1)) == 0 - True """ - cdef int i, ret, sf, sg, val - cdef ETuple f, g - ret = 0 - for i in range(min(len(tleft),len(tright))): - f, g = tleft[i][0], tright[i][0] - if f == g: - if tleft[i][1] != tright[i][1]: - ret = -1 + 2*(tleft[i][1] > tright[i][1]) - else: - sf, sg = 0, 0 - for val in f.nonzero_values(sort=False): - sf += val - for val in g.nonzero_values(sort=False): - sg += val - ret = -1 + 2*(sf > sg or ( sf == sg and f.reversed() < g.reversed() )) - if ret != 0: - return ret - return len(tleft) - len(tright) + cdef ETuple exp + cdef int i, l, nnz + key = list() + # for exp, c in eq_tup: + # key.extend([sum(exp.nonzero_values(sort=False)),exp.reversed().emul(-1),c._coefficients()]) + # for exp, c in eq_tup: + # #Compare by term degree + # key.append(exp.unweighted_degree()) + # revlex_exp = exp.reversed().emul(-1) + # #Then by term w.r.t. revlex order + # revlex_exp_key = list() + # for i from 0 <= i < exp._nonzero: + # #Reverse tuple and negate values + # # key.append(-(exp._length - exp._data[2*(exp._nonzero-i-1)] - 1)) + # # key.append(-exp._data[2*(exp._nonzero-i-1)+1]) + # key.append(-exp._data[2*i]) + # key.append(exp._data[2*i+1]) + # #Finally by coefficient + # key.extend(c._coefficients()) + for exp, c in eq_tup: + #Compare by term degree + key.append(exp.unweighted_degree()) + #Next compare by term w.r.t. revlex order + l = exp._length + nnz = exp._nonzero + for i from 0 <= i < nnz: + # key.append(l-1-exp._data[2*(nnz-i-1)]) + # key.append(-exp._data[2*(nnz-i-1)+1]) + # Try sorting in lex order instead + key.append(-exp._data[2*i]) + key.append(exp._data[2*i+1]) + return tuple(key) From 6ceb6d9a3c97f760c21b3b44396508fbe7b3f53e Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 19 Mar 2021 08:03:06 -0700 Subject: [PATCH 028/414] f_matrix documentation added to reference manual --- src/doc/en/reference/combinat/module_list.rst | 1 + src/doc/en/reference/references/index.rst | 7 +- src/sage/combinat/root_system/__init__.py | 1 + src/sage/combinat/root_system/f_matrix.py | 423 +++++++++++------- .../fast_parallel_fmats_methods.pyx | 9 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 21 +- src/sage/combinat/root_system/fusion_ring.py | 120 ++++- .../combinat/root_system/poly_tup_engine.pyx | 25 -- 8 files changed, 393 insertions(+), 214 deletions(-) diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index a95069590b4..3cc1f591827 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -290,6 +290,7 @@ Comprehensive Module list sage/combinat/root_system/weight_space sage/combinat/root_system/weyl_characters sage/combinat/root_system/fusion_ring + sage/combinat/root_system/f_matrix sage/combinat/root_system/weyl_group sage/combinat/rooted_tree sage/combinat/rsk diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 808d2fa07c1..76fc6693f18 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1731,7 +1731,9 @@ REFERENCES: curves. preprint, 2005. .. [CHW2015] Shawn X.; Hong, Seung-Moon; Wang, Zhenghan Universal quantum computation - with weakly integral anyons. Quantum Inf. Process. 14 (2015), no. 8, 2687–2727. + with weakly integral anyons. Quantum Inf. Process. 14 (2015), no. 8, 2687–2727. + + .. _ref-D: **D** @@ -5476,6 +5478,9 @@ REFERENCES: calculus*, Algebra and Number Theory 3 (2009), 121-148, https://projecteuclid.org/euclid.ant/1513797353 +.. [TTWL2009] Trebst, Troyer, Wang and Ludwig, A short introduction to + Fibonacci anyon models, :arxiv:`0902.3275`. + .. _ref-U: **U** diff --git a/src/sage/combinat/root_system/__init__.py b/src/sage/combinat/root_system/__init__.py index b04c503fad1..694205392d1 100644 --- a/src/sage/combinat/root_system/__init__.py +++ b/src/sage/combinat/root_system/__init__.py @@ -78,6 +78,7 @@ - :ref:`sage.combinat.root_system.weyl_characters` - :ref:`sage.combinat.root_system.fusion_ring` +- :ref:`sage.combinat.root_system.f_matrix` - :ref:`sage.combinat.root_system.integrable_representations` - :ref:`sage.combinat.root_system.branching_rules` - :ref:`sage.combinat.root_system.hecke_algebra_representation` diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 08563fade2e..9df09e88ab7 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -47,6 +47,18 @@ class FMatrix(): - ``FR`` -- a FusionRing. + The :class:`FusionRing` or Verlinde algebra is the + Grothendieck ring of a modular tensor category [Baki2001]_. + Such categories arise in conformal field theory or in the + representation theories of affine Lie algebras, or + quantum groups at roots of unity. They have applications + to low dimensional topology and knot theory, to conformal + field theory and to topological quantum computing. The + FusionRing captures much information about a fusion category, + but to complete the picture, the F-matrices or 6j-symbols + are needed. For example these are required in order to + construct braid group representations. + We only undertake to compute the F-matrix if the FusionRing is *multiplicity free* meaning that the Fusion coefficients `N^{ij}_k` are bounded @@ -104,7 +116,7 @@ class FMatrix(): definition and [Bond2007]_ Section 2.5 for a discussion of how to compute the F-matrix. In addition to [Bond2007]_ worked out F-matrices may be found in - [RoStWa2009]_ and [CuWa2015]_. + [RoStWa2009]_ and [CHW2015]_. The F-matrix is only determined up to a *gauge*. This is a family of embeddings `C\to A\otimes B` for @@ -228,7 +240,10 @@ class FMatrix(): EXAMPLES:: - + sage: f = FMatrix(FusionRing("B3",2)) + sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # long time + sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # long time + True """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): @@ -244,25 +259,20 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab print ("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) self._poly_ring.inject_variables(get_main_globals()) self._var_to_sextuple, self._fvars = self.findcases(output=True) + self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } self._singles = self.singletons() - #Initialize list of defining equations - self.ideal_basis = list() - #Base field attributes self._field = self._FR.field() r = self._field.defining_polynomial().roots(ring=QQbar,multiplicities=False)[0] self._qqbar_embedding = self._field.hom([r],QQbar) self._non_cyc_roots = list() - #Solver state attributes - #Initialize empty set of solved F-symbols - self.solved = set() - self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } + #Useful solver state attributes + self._solved = set() self._ks = dict() self._nnz = self._get_known_nonz() - self._known_vals = dict() - self.symbols_known = False + self._chkpt_status = -1 #Multiprocessing attributes self.mp_thresh = 10000 @@ -284,7 +294,7 @@ def remaining_vars(self): """ Return a list of unknown F-symbols (reflects current stage of computation) """ - return [var for var in self._poly_ring.gens() if var not in self.solved] + return [var for var in self._poly_ring.gens() if var not in self._solved] def clear_equations(self): """ @@ -296,9 +306,38 @@ def clear_vars(self): """ Clear the set of variables. Also reset the set of solved F-symbols. """ - self._FR._basecoer = None self._fvars = { self._var_to_sextuple[key] : key for key in self._var_to_sextuple } - self.solved = set() + self._solved = set() + + def _reset_solver_state(self): + """ + Reset solver state and clear relevant cache. + """ + self._FR._basecoer = None + self._field = self._FR.field() + self._update_poly_ring_base_field(field=self._field) + self._chkpt_status = -1 + self.clear_vars() + self.clear_equations() + self._ks = dict() + self._nnz = self._get_known_nonz() + + #Clear relevant caches + [x.q_dimension.clear_cache() for x in self._FR.basis()] + self._FR.r_matrix.clear_cache() + self._FR.s_ij.clear_cache() + + def _update_poly_ring_base_field(self,field): + """ + Change base field of PolynomialRing and the corresponding + index attributes + """ + new_poly_ring = self._poly_ring.change_ring(field) + nvars = self._poly_ring.ngens() + #Do some appropriate conversions + self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } + self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } + self._poly_ring = new_poly_ring def fmat(self, a, b, c, d, x, y, data=True): """ @@ -459,8 +498,9 @@ def findcases(self,output=False): return i def singletons(self): - """ - Find x_i that are automatically nonzero, because their F-matrix is 1x1 + r""" + Find `x_i` that are automatically nonzero, because their F-matrix is + `1 \\times 1`. """ ret = [] for (a, b, c, d) in list(product(self._FR.basis(), repeat=4)): @@ -600,9 +640,10 @@ def get_coerce_map_from_fr_cyclotomic_field(self): def get_fmats_in_alg_field(self): r""" - Return F-symbols as elements of the ``AlgebraicField``. This method uses - the embedding defined by :meth:``self.get_qqbar_embedding`` to coerce - F-symbols into QQbar. + Return F-symbols as elements of the :class:`AlgebraicField` ``QQbar``. + This method uses the embedding defined by + :meth:``self.get_qqbar_embedding`` to coerce + F-symbols into ``QQbar``. """ return { sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items() } @@ -621,7 +662,7 @@ def _get_known_vals(self): Construct a dictionary of ``idx``, ``known_val`` pairs used for substituting into remaining equations. """ - return { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in self.solved } + return { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in self._solved } def _get_known_sq(self,eqns=None): r""" @@ -657,13 +698,13 @@ def _is_univariate_in_unknown(self,monom_exp): """ Determine if monomial exponent is univariate in an unknown F-symbol """ - return len(monom_exp.nonzero_values()) == 1 and monom_exp.nonzero_positions()[0] not in self.solved + return len(monom_exp.nonzero_values()) == 1 and monom_exp.nonzero_positions()[0] not in self._solved def _is_uni_linear_in_unkwown(self,monom_exp): """ Determine if monomial exponent is univariate and linear in an unknown F-symbol """ - return monom_exp.nonzero_values() == [1] and monom_exp.nonzero_positions()[0] not in self.solved + return monom_exp.nonzero_values() == [1] and monom_exp.nonzero_positions()[0] not in self._solved ############################## ### Variables partitioning ### @@ -683,7 +724,7 @@ def largest_fmat_size(self): def get_fvars_by_size(self,n,indices=False): r""" - Return the set of F-symbols that are entries of an `n \times n` matrix + Return the set of F-symbols that are entries of an `n \\times n` matrix `F^{a,b,c}_d`. INPUT: @@ -712,7 +753,7 @@ def get_fvars_by_size(self,n,indices=False): (f2, f2, f2, f2, f1, f1)} """ fvars_copy = deepcopy(self._fvars) - solved_copy = deepcopy(self.solved) + solved_copy = deepcopy(self._solved) self.clear_vars() var_set = set() for quadruple in product(self._FR.basis(),repeat=4): @@ -721,7 +762,7 @@ def get_fvars_by_size(self,n,indices=False): if F.nrows() == n and F.coefficients() != [1]: var_set.update(F.coefficients()) self._fvars = fvars_copy - self.solved = solved_copy + self._solved = solved_copy if indices: return { self._var_to_idx[fx] for fx in var_set } return { self._var_to_sextuple[fx] for fx in var_set } @@ -730,6 +771,30 @@ def get_fvars_by_size(self,n,indices=False): ### Checkpoint utilities ### ############################ + def save_fvars(self, filename): + r""" + Save computed F-symbols for later use. This method should only be used + AFTER successfully one of the solvers, e.g. + :meth:`find_cyclotomic_solution` or :meth:`find_orthogonal_solution`. + + Specify the ``filename`` as a string for storing computed F-symbols in a + pickle file. + """ + with open(filename, 'wb') as f: + pickle.dump(self._fvars, f) + + def load_fvars(self, filename): + r""" + Load previously computed F-symbols from a pickle file generated by + :meth:`save_fvars`. This method does not work with intermediate + checkpoint pickles; it only works with pickles containing ALL F-symbols. + """ + with open(filename, 'rb') as f: + self._fvars = pickle.load(f) + #Update class state attributes + self.symbols_known = True + self._solved = set(range(self._poly_ring.ngens())) + def get_fr_str(self): """ Auto-generate identifying key for saving results @@ -743,46 +808,58 @@ def get_fr_str(self): ct = self._FR.cartan_type() return ct.letter + str(ct.n) + str(self._FR.fusion_level()) - def save_fvars(self,filename): + def _checkpoint(self,do_chkpt,status,verbose=True): """ - Save current variables state + Pickle current solver state """ + if not do_chkpt: + return + filename = "fmatrix_solver_checkpoint_" + self.get_fr_str() + ".pickle" + eqns = self.ideal_basis + self._add_square_fixers(output=True) with open(filename, 'wb') as f: - pickle.dump([self._fvars, self.solved], f) + pickle.dump([self._fvars, self._solved, eqns, status], f) + if verbose: + print(f"Checkpoint {status} reached!") - def load_fvars(self,save_dir=""): + def _restore_state(self,filename): r""" Load solver state from file. Use this method both for warm-starting :meth:`find_orthogonal_solution` and to load pickled results. - - If provided, the optional parameter ``save_dir`` must have a trailing - forward slash e.g. "my_dir/" """ - with open(save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle", 'rb') as f: - self._fvars, self.solved = pickle.load(f) + with open(filename, 'rb') as f: + state = pickle.load(f) + #Loading saved results pickle + if type(state) == type(dict()): + self.load_fvars(filename) + self._chkpt_status = 7 + return + self._fvars, self._solved, self.ideal_basis, self._chkpt_status = state + self._update_reduction_params() ################# ### MapReduce ### ################# - def map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): - """ + def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): + r""" Apply the given mapper to each element of the given input iterable and - return the results (with no duplicates) in a list. This method applies the - mapper in parallel if a worker_pool is provided. + return the results (with no duplicates) in a list. This method applies + the mapper in parallel if a worker_pool is provided. INPUT: - -``mapper`` -- string specifying the name of a function defined in the - ``fast_parallel_fmats_methods`` module. + -``mapper`` -- string specifying the name of a function defined in + the ``fast_parallel_fmats_methods`` module. NOTES: - If worker_pool is not provided, function maps and reduces on a single process. - If worker_pool is provided, the function attempts to determine whether it should - use multiprocessing based on the length of the input iterable. If it can't determine - the length of the input iterable then it uses multiprocessing with the default chunksize of 1 - if chunksize is not explicitly provided. + If ``worker_pool`` is not provided, function maps and reduces on a + single process. + If ``worker_pool`` is provided, the function attempts to determine + whether it should use multiprocessing based on the length of the + input iterable. If it can't determine the length of the input + iterable then it uses multiprocessing with the default chunksize of + `1` if chunksize is not explicitly provided. """ if mp_thresh is None: mp_thresh = self.mp_thresh @@ -862,7 +939,7 @@ def get_defining_equations(self,option,worker_pool=None,output=True): """ n_proc = worker_pool._processes if worker_pool is not None else 1 params = [(child_id, n_proc) for child_id in range(n_proc)] - eqns = self.map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + eqns = self._map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) if output: return [self._tup_to_fpoly(p) for p in eqns] self.ideal_basis.extend(eqns) @@ -887,6 +964,8 @@ def _tup_to_fpoly(self,eq_tup): def _solve_for_linear_terms(self,eqns=None): """ Solve for a linear term occurring in a two-term equation. + + Also solve for variables that are zero. """ if eqns is None: eqns = self.ideal_basis @@ -898,7 +977,7 @@ def _solve_for_linear_terms(self,eqns=None): if self._is_univariate_in_unknown(m): var = m.nonzero_positions()[0] self._fvars[self._var_to_sextuple[self._poly_ring.gen(var)]] = tuple() - self.solved.add(var) + self._solved.add(var) linear_terms_exist = True if len(eq_tup) == 2: monomials = [m for m, c in eq_tup] @@ -910,7 +989,7 @@ def _solve_for_linear_terms(self,eqns=None): rhs_key = monomials[other] rhs_coeff = -eq_tup[other][1] / eq_tup[this][1] self._fvars[self._var_to_sextuple[self._poly_ring.gen(var)]] = ((rhs_key,rhs_coeff),) - self.solved.add(var) + self._solved.add(var) linear_terms_exist = True return linear_terms_exist @@ -922,12 +1001,12 @@ def _backward_subs(self): for var in reversed(self._poly_ring.gens()): sextuple = self._var_to_sextuple[var] rhs = self._fvars[sextuple] - d = { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in variables(rhs) if var_idx in self.solved } + d = { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in variables(rhs) if var_idx in self._solved } if d: kp = compute_known_powers(get_variables_degrees([rhs]), d, one) self._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one),self._ks).items()) - def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): + def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): """ Update reduction parameters in all processes """ @@ -939,10 +1018,10 @@ def update_reduction_params(self,eqns=None,worker_pool=None,children_need_update if worker_pool is not None and children_need_update: #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes - new_data = [(self._fvars,self.solved,self._ks,self._var_degs)]*n_proc - self.map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + new_data = [(self._fvars,self._solved,self._ks,self._var_degs)]*n_proc + self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) - def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=True): + def _triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=True): """ Perform triangular elimination of linear terms in two-term equations until no such terms exist For optimal usage of TRIANGULAR elimination, pass in a SORTED list of equations @@ -953,7 +1032,6 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T ret = False if required_vars is None: required_vars = self._poly_ring.gens() - # poly_sortkey = cmp_to_key(poly_tup_cmp) poly_sortkey = poly_tup_sortkey_degrevlex #Unzip polynomials @@ -969,8 +1047,8 @@ def triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=T if req_vars_known: return 1 #Compute new reduction params, send to child processes if any, and update eqns - self.update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) - eqns = self.map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool) + self._update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) + eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool) eqns.sort(key=poly_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) @@ -1022,13 +1100,20 @@ def partition_eqns(self,graph,eqns=None,verbose=True): print(graph.connected_components_sizes()) return partition - def add_square_fixers(self): + def _add_square_fixers(self,output=False): """ Add square fixing equations back to ideal basis """ + sq_fixers = list() + n = self._poly_ring.ngens() + one = self._field.one() for fx, rhs in self._ks.items(): - if fx not in self.solved: - self.ideal_basis.append(poly_to_tup(self._poly_ring.gen(fx)**2 - rhs)) + if fx not in self._solved: + lt = (ETuple({ fx : 2 },n), one) + sq_fixers.append((lt, (ETuple({},n), -rhs))) + if output: + return sq_fixers + self.ideal_basis.extend(sq_fixers) def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): """ @@ -1058,7 +1143,7 @@ def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose= else: small_comps.append(comp_eqns) input_iter = zip_longest(small_comps,[],fillvalue=term_order) - small_comp_gb = self.map_triv_reduce('compute_gb',input_iter,worker_pool=worker_pool,chunksize=1,mp_thresh=50) + small_comp_gb = self._map_triv_reduce('compute_gb',input_iter,worker_pool=worker_pool,chunksize=1,mp_thresh=50) ret = small_comp_gb + temp_eqns return ret @@ -1120,7 +1205,7 @@ def attempt_number_field_computation(self): return False return True - def get_explicit_solution(self,eqns=None,verbose=True): + def _get_explicit_solution(self,eqns=None,verbose=True): """ When this method is called, the solution is already found in terms of Groeber basis. A few degrees of freedom remain. @@ -1129,7 +1214,9 @@ def get_explicit_solution(self,eqns=None,verbose=True): """ if eqns is None: eqns = self.ideal_basis - self.add_square_fixers() + #Don't add square fixers when warm starting from a late-stage checkpoint + if self._chkpt_status < 5: + self._add_square_fixers() eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) F = self._field @@ -1163,15 +1250,11 @@ def get_explicit_solution(self,eqns=None,verbose=True): #Attempt to compute smallest number field containing all the F-symbols #If calculation takes too long, we use QQbar as the base field if self.attempt_number_field_computation(): + if verbose: + print("Computing appropriate NumberField...") roots = [self._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] self._field, bf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots,minimal=True) else: - # proc = Pool(1) - # input_args = ((('get_appropriate_number_field',id(self)),non_cyclotomic_roots),) - # p = proc.apply_async(executor,input_args) - # try: - # self._field, bf_elts, self._qqbar_embedding = p.get(timeout=100) - # except TimeoutError: self._field = QQbar bf_elts = [self._qqbar_embedding(F.gen())] bf_elts += [rhs for fx,rhs in non_cyclotomic_roots] @@ -1186,17 +1269,13 @@ def get_explicit_solution(self,eqns=None,verbose=True): for i, elt in enumerate(bf_elts): numeric_fvars[non_cyclotomic_roots[i][0]] = elt - #Do some appropriate conversions - new_poly_ring = self._poly_ring.change_ring(self._field) - nvars = self._poly_ring.ngens() - self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } - self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } - self._poly_ring = new_poly_ring + #Update polynomial ring + self._update_poly_ring_base_field(self._field) #Ensure all F-symbols are known - self.solved.update(numeric_fvars) + self._solved.update(numeric_fvars) nvars = self._poly_ring.ngens() - assert len(self.solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self.solved)]) + assert len(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self._solved)]) #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } @@ -1204,60 +1283,75 @@ def get_explicit_solution(self,eqns=None,verbose=True): self._fvars[self._var_to_sextuple[self._poly_ring.gen(fx)]] = ((ETuple({},nvars),rhs),) self._backward_subs() self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } - self.clear_equations() #Update base field attributes self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() - for x in self._FR.basis(): - x.q_dimension.clear_cache() - def find_orthogonal_solution(self,checkpoint=False,save_results=False,save_dir="",verbose=True,use_mp=True): + def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="",verbose=True,use_mp=True): r""" Find an orthogonal solution to the pentagon equations associated to the monoidal category represented by ``self``. INPUT: - -``checkpoint`` -- (optional) a boolean indicating whether the computation should - be checkpointed. Depending on the associated ``CartanType``, the computation - may take hours to complete. For large examples, checkpoints are - recommended. This method supports "warm" starting, so the calculation - may be resumed from a checkpoint. Checkpoints store necessary state in - the form of pickle files. - - -``save_results`` -- (optional) a boolean indicating whether the F-symbols - should be stored to file as a pickle for later use. - - -``save_dir`` -- (optional) a string specifying the directory in which - to save the pickled F-symbols. If used, the string must have a trailing - forward slash e.g. "my_dir/" - The file name is "saved_fvars_" + ``key`` + .pickle, where - key is computed by :meth:`get_fr_str`. The file name is fixed to allow - for automatic loading. The ``save_dir`` is also used to specify the - location of a checkpoint pickle, if one exists. - - -``use_mp`` -- (optional) a boolean indicating whether to use + - ``checkpoint`` -- (default: ``False``) a boolean indicating whether + the computation should be checkpointed. Depending on the associated + ``CartanType``, the computation may take hours to complete. For + large examples, checkpoints are recommended. This method supports + "warm" starting, so the calculation may be resumed from a checkpoint, + using the ``warm_start`` option. + + Checkpoints store necessary state in the pickle file + ``"fmatrix_solver_checkpoint_" + key + ".pickle"``, where ``key`` + is the result of :meth:`get_fr_str`. + + Generated checkpoint pickles are automatically deleted when the + solver runs successfully. + + - ``save_results`` -- (optional) a string indicating the name of a + pickle file in which to store calculated F-symbols for later use. + + If ``save_results`` is not provided (default), results are not + stored to file. + + The F-symbols may be saved to file after running the solver using + :meth:`save_fvars`. + + - ``warm_start`` -- (optional) a string indicating the name of a pickle + file containing checkpointed solver state. This file must have been + produced by a previous call to the solver using the ``checkpoint`` + option. + + If no file name is provided, the calculation begins from scratch. + + - ``use_mp`` -- (default: ``True``) a boolean indicating whether to use multiprocessing to speed up calculation. The default value - ``True`` is recommended, since parallel processing yields results - much quicker. + ``True`` is highly recommended, since parallel processing yields + results much more quickly. OUTPUT: - This method returns ``None``. If the solver ran successfully, the + This method returns ``None``. If the solver runs successfully, the results may be accessed through various methods, such as - :meth:``get_fvars``, :meth:``fmatrix``, :meth:``fmat``, etc. + :meth:`get_fvars`, :meth:`fmatrix`, :meth:`fmat`, etc. In many cases the F-symbols obtained are in fact real. In any case, the F-symbols are obtained as elements of the associated ``FusionRing``'s ``CyclotomicField``, a computed ``NumberField``, or ``QQbar``. Currently, the output field is determined based on the ``CartanType`` - associated to ``self``. See :meth:``attempt_number_field_computation`` + associated to ``self``. See :meth:`attempt_number_field_computation` for details. """ + self._reset_solver_state() + + #Resume computation from checkpoint + if warm_start: + self._restore_state(warm_start) + #Loading from a pickle with solved F-symbols + if self._chkpt_status > 5: return + #Set multiprocessing parameters. Context can only be set once, so we try to set it - self.clear_equations() - self.clear_vars() try: set_start_method('fork') except RuntimeError: @@ -1266,61 +1360,78 @@ def find_orthogonal_solution(self,checkpoint=False,save_results=False,save_dir=" if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) - #Attempt warm-start - try: - self.load_fvars(save_dir) - except FileNotFoundError: - pass - - #Set up hexagon equations and orthogonality constraints poly_sortkey = poly_tup_sortkey_degrevlex - self.get_orthogonality_constraints(output=False) - self.get_defining_equations('hexagons',worker_pool=pool,output=False) - if verbose: - print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) + if self._chkpt_status < 1: + #Set up hexagon equations and orthogonality constraints + self.get_orthogonality_constraints(output=False) + self.get_defining_equations('hexagons',worker_pool=pool,output=False) - #Set up equations graph. Find GB for each component in parallel. Eliminate variables - self.ideal_basis = self.par_graph_gb(worker_pool=pool,verbose=verbose) - self.ideal_basis.sort(key=poly_sortkey) - self.triangular_elim(worker_pool=pool,verbose=verbose) + #Report progress + if verbose: + print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) - #Report progress and checkpoint! - if verbose: - print("Hex elim step solved for {} / {} variables".format(len(self.solved), len(self._poly_ring.gens()))) - filename = save_dir + "saved_fvars_" + self.get_fr_str() + ".pickle" - if checkpoint: self.save_fvars(filename) + self._checkpoint(checkpoint,1,verbose=verbose) - #Update reduction parameters, also in children if any - self.update_reduction_params(worker_pool=pool,children_need_update=True) + if self._chkpt_status < 2: + #Set up equations graph. Find GB for each component in parallel. Eliminate variables + self.ideal_basis = self.par_graph_gb(worker_pool=pool,verbose=verbose) + self.ideal_basis.sort(key=poly_sortkey) + self._triangular_elim(worker_pool=pool,verbose=verbose) - #Set up pentagon equations in parallel, simplify, and eliminate variables - self.get_defining_equations('pentagons',worker_pool=pool,output=False) - if verbose: - print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) - self.ideal_basis.sort(key=poly_sortkey) - self.triangular_elim(worker_pool=pool,verbose=verbose) + #Update reduction parameters, also in children if any + self._update_reduction_params(worker_pool=pool,children_need_update=True) - #Report progress and checkpoint! - if verbose: - print("Pent elim step solved for {} / {} variables".format(len(self.solved), len(self._poly_ring.gens()))) - if checkpoint: self.save_fvars(filename) + #Report progress + if verbose: + print("Hex elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) + + self._checkpoint(checkpoint,2,verbose=verbose) + + if self._chkpt_status < 3: + #Set up pentagon equations in parallel + self.get_defining_equations('pentagons',worker_pool=pool,output=False) + self.ideal_basis.sort(key=poly_sortkey) + + #Report progress + if verbose: + print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) + + self._checkpoint(checkpoint,3,verbose=verbose) + + #Simplify and eliminate variables + if self._chkpt_status < 4: + self._triangular_elim(worker_pool=pool,verbose=verbose) + + #Report progress + if verbose: + print("Pent elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) + + self._checkpoint(checkpoint,4,verbose=verbose) + + #Try adding degrevlex gb -> elim loop until len(ideal_basis) does not change #Close worker pool to free resources - if pool is not None: pool.close() + if pool is not None: + pool.close() #Set up new equations graph and compute variety for each component - self.ideal_basis = self.par_graph_gb(term_order="lex",verbose=verbose) - self.ideal_basis.sort(key=poly_sortkey) - self.triangular_elim(verbose=verbose) - if checkpoint: self.save_fvars(filename) - self.get_explicit_solution(verbose=verbose) + if self._chkpt_status < 5: + self.ideal_basis = self.par_graph_gb(term_order="lex",verbose=verbose) + self.ideal_basis.sort(key=poly_sortkey) + self._triangular_elim(verbose=verbose) + + self._checkpoint(checkpoint,5,verbose=verbose) + + #Find numeric values for each F-symbol + self._get_explicit_solution(verbose=verbose) #The calculation was successful, so we may delete checkpoints - self.symbols_known = True + self._chkpt_status = 7 + self.clear_equations() if checkpoint: - os.remove(filename) + os.remove("fmatrix_solver_checkpoint_"+self.get_fr_str()+".pickle") if save_results: - self.save_fvars(filename) + self.save_fvars(save_results) ######################### ### Cyclotomic method ### @@ -1331,11 +1442,11 @@ def fix_gauge(self, algorithm=''): Fix the gauge by forcing F-symbols not already fixed to equal 1. This method should be used AFTER adding hex and pentagon eqns to ideal_basis """ - while len(self.solved) < len(self._poly_ring.gens()): + while len(self._solved) < len(self._poly_ring.gens()): #Get a variable that has not been fixed #In ascending index order, for consistent results for var in self._poly_ring.gens(): - if var not in self.solved: + if var not in self._solved: break #Fix var = 1, substitute, and solve equations @@ -1355,16 +1466,16 @@ def substitute_degree_one(self, eqns=None): #Substitute known value from univariate degree 1 polynomial or, #Following Bonderson, p. 37, solve linear equation with two terms #for one of the variables - if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self.solved: + if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self._solved: self._fvars[self._var_to_sextuple[eq.lm()]] = -sum(c * m for c, m in zip(eq.coefficients()[1:], eq.monomials()[1:])) / eq.lc() #Add variable to set of known values and remove this equation new_knowns.add(eq.lm()) useless.add(eq) #Update fvars depending on other variables - self.solved.update(new_knowns) + self._solved.update(new_knowns) for sextuple, rhs in self._fvars.items(): - d = { var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self.solved } + d = { var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self._solved } if d: self._fvars[sextuple] = rhs.subs(d) return new_knowns, useless @@ -1373,7 +1484,7 @@ def update_equations(self): """ Update ideal_basis equations by plugging in known values """ - special_values = { known : self._fvars[self._var_to_sextuple[known]] for known in self.solved } + special_values = { known : self._fvars[self._var_to_sextuple[known]] for known in self._solved } self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) self.ideal_basis.discard(0) @@ -1430,8 +1541,8 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o [] """ - self.clear_vars() - self.clear_equations() + self._reset_solver_state() + if equations is None: if verbose: print("Setting up hexagons and pentagons...") @@ -1484,7 +1595,7 @@ def verify_pentagons(self,use_mp=True,prune=False): pool = None n_proc = pool._processes if pool is not None else 1 params = [(child_id,n_proc) for child_id in range(n_proc)] - pe = self.map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) + pe = self._map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) if np.all(np.isclose(np.array(pe),0,atol=1e-7)): print("Success!!! Found valid F-symbols for {}".format(self._FR)) pe = None diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 7e9c5ba980a..2e7c31fe9a2 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -120,10 +120,10 @@ cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = r_matrix(a,c,e)*_fmat(fvars,Nk_ij,id_anyon,a,c,b,d,e,g)*r_matrix(b,c,g) + lhs = r_matrix(a,c,e,base_coercion=False)*_fmat(fvars,Nk_ij,id_anyon,a,c,b,d,e,g)*r_matrix(b,c,g,base_coercion=False) rhs = 0 for f in basis: - rhs += _fmat(fvars,Nk_ij,id_anyon,c,a,b,d,e,f)*r_matrix(f,c,d)*_fmat(fvars,Nk_ij,id_anyon,a,b,c,d,f,g) + rhs += _fmat(fvars,Nk_ij,id_anyon,c,a,b,d,e,f)*r_matrix(f,c,d,base_coercion=False)*_fmat(fvars,Nk_ij,id_anyon,a,b,c,d,f,g) return lhs-rhs @cython.wraparound(False) @@ -189,7 +189,6 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) cdef dict fvars = factory._fvars - r_matrix = factory._FR.r_matrix _Nk_ij = factory._FR.Nk_ij id_anyon = factory._FR.one() cdef NumberFieldElement_absolute one = factory._field.one() @@ -249,11 +248,11 @@ cpdef update_child_fmats(factory, tuple data_tup): """ One-to-all communication used to update FMatrix object after each triangular elim step. We must update the algorithm's state values. These are: - _fvars, solved, _ks, _var_degs, _nnz, and _kp. + _fvars, _solved, _ks, _var_degs, _nnz, and _kp. """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object - factory._fvars, factory.solved, factory._ks, factory._var_degs = data_tup + factory._fvars, factory._solved, factory._ks, factory._var_degs = data_tup factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index a55bc74b83a..4e587f1955f 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -24,10 +24,15 @@ worker_results = list() def executor(params): """ - Execute a function defined in this module (sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn) - in a worker process, and supply the factory parameter by constructing a reference - to the FMatrix object in the worker's memory adress space from its id - ## NOTE: When the parent process is forked, each worker gets a copy of + Execute a function defined in this module + (sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn) + in a worker process, and supply the factory parameter by constructing a + reference to the FMatrix object in the worker's memory adress space + from its id. + + NOTES: + + When the parent process is forked, each worker gets a copy of every global variable. The virtual memory address of object X in the parent process equals the VIRTUAL memory address of the copy of object X in each worker, so we may construct references to forked copies of X @@ -102,10 +107,10 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): return entry cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): - """ - Compute the xi, xj entry of the braid generator on the right-most strands, - corresponding to the tree b -> (xi # a) -> (a # a) # a, which results in a - sum over j of trees b -> xj -> (a # a) # (a # a) + r""" + Compute the `xi`, `xj` entry of the braid generator on the two right-most + strands, corresponding to the tree b -> (xi # a) -> (a # a) # a, which + results in a sum over j of trees b -> xj -> (a # a) # (a # a) ..warning: This method assumes F-matrices are orthogonal diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 4031c3e10ea..9acc5807bec 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -492,9 +492,59 @@ def field(self): sage: FusionRing("B2",2).field() Cyclotomic Field of order 40 and degree 16 """ - if self._field is None: - self._field = CyclotomicField(4 * self._cyclotomic_order) - return self._field + # if self._field is None: + # self._field = CyclotomicField(4 * self._cyclotomic_order) + # return self._field + return CyclotomicField(4 * self._cyclotomic_order) + + def fvars_field(self): + r""" + Return a field containing the ``CyclotomicField`` computed by + :meth:`field` as well as all the F-symbols of the associated + ``FMatrix`` factory object. + + This method is only available if ``self`` is multiplicity-free. + + OUTPUT: + + Depending on the ``CartanType`` associated to ``self`` and whether + a call to an F-matrix solver has been made, this method + will return the same field as :meth:`field`, a ``NumberField``, + or the ``AlgebraicField`` ``QQbar``. + See :meth:`FMatrix.attempt_number_field_computation` for more details. + + Before running an F-matrix solver, the output of this method matches + that of :meth:`field`. However, the output may change upon successfully + computing F-symbols. Requesting braid generators triggers a call to + :meth:`FMatrix.find_orthogonal_solution`, so the output of this method + may change after such a computation. + + By default, the output of methods like :meth:`r_matrix`, + :meth:`s_matrix`, :meth:`twists_matrix`, etc. will lie in the + ``fvars_field``, unless the ``base_coercion`` option is set to + ``False``. + + This method does not trigger a solver run. + + EXAMPLES:: + + sage: A13 = FusionRing("A1",3,fusion_labels="a",inject_variables=True) + sage: A13.fvars_field() + Cyclotomic Field of order 40 and degree 16 + sage: A13.field() + Cyclotomic Field of order 40 and degree 16 + sage: a2**4 + 2*a0 + 3*a2 + sage: comp_basis, sig = A13.get_braid_generators(a2,a2,4,verbose=False) + sage: A13.fvars_field() + Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 + sage: a2.q_dimension().parent() + Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 + sage: A13.field() + Cyclotomic Field of order 40 and degree 16 + """ + if self.is_multiplicity_free(): + return self.fmats.field() def root_of_unity(self, r, base_coercion=True): r""" @@ -1061,15 +1111,18 @@ def _emap(self,mapper,input_args,worker_pool=None): INPUT: - ``mapper`` -- a string specifying the name of a function defined in the - fast_parallel_fmats_methods module. - - ``input_args`` -- a tuple holding arguments to be passed to mapper - - ##NOTES: - If worker_pool is not provided, function maps and reduces on a single process. - If worker_pool is provided, the function attempts to determine whether it should - use multiprocessing based on the length of the input iterable. If it can't determine - the length of the input iterable then it uses multiprocessing with the default chunksize of 1 - if chunksize is not explicitly provided. + ``fast_parallel_fusion_ring_braid_repn`` module. + - ``input_args`` -- a tuple of arguments to be passed to mapper + + NOTES: + + If ``worker_pool`` is not provided, function maps and reduces on a + single process. + If ``worker_pool`` is provided, the function attempts to determine + whether it should use multiprocessing based on the length of the + input iterable. If it can't determine the length of the input + iterable then it uses multiprocessing with the default chunksize of + `1` if chunksize is not explicitly provided. """ n_proc = worker_pool._processes if worker_pool is not None else 1 input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] @@ -1091,17 +1144,40 @@ def _emap(self,mapper,input_args,worker_pool=None): results = list(results) return results - def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=True,verbose=True): + def get_braid_generators(self, + fusing_anyon, + total_charge_anyon, + n_strands, + checkpoint=False, + save_results="", + warm_start="", + use_mp=True, + verbose=True): """ - INPUT: + Compute generators of the Artin braid group on `n=` ``n_strands`` + strands. If `a` = ``fusing_anyon`` and `b` = ``total_charge_anyon`` + the generators are endomorphisms of `\\text{Hom}(b, a^n)`. - Compute generators of the Artin braid group on `n=` n_strands strands. If - fusing_anyon = a and total_charge_anyon = b, the generators are - endomorphisms of `\\text{Hom}(b, a^n)` + INPUT: - ``fusing_anyon`` -- a basis element of self - ``total_charge_anyon`` -- a basis element of self - ``n_strands`` -- a positive integer greater than 2 + - ``checkpoint`` -- (optional) a boolean indicating whether the + F-matrix solver should pickle checkpoints. + - ``save_results`` -- (optional) a string indicating the name of + a file in which to pickle computed F-symbols for later use. + - ``warm_start`` -- (optional) a string indicating the name of a + pickled checkpoint file to "warm" start the F-matrix solver. + The pickle may be a checkpoint generated by the solver, or + a file containing solver results. If all F-symbols are known, + we don't run the solver again. + - ``use_mp`` -- (optional) a boolean indicating whether to use + multiprocessing to speed up the computation. This is highly + recommended. + + For more information on the optional parameters, see + :meth:`find_orthogonal_solution` of the :class:`FMatrix` module. Given a simple object in the fusion category, here called ``fusing_anyon`` allowing the universal R-matrix to act on adjacent @@ -1110,6 +1186,8 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T be decomposed over another anyon, here called ``total_charge_anyon``. See [CHW2015]_. + OUTPUT: + The method outputs a pair of data ``(comp_basis,sig)`` where ``comp_basis`` is a list of basis elements of the braid group module, parametrized by a list of fusion ring elements describing @@ -1145,8 +1223,12 @@ def get_braid_generators(self,fusing_anyon,total_charge_anyon,n_strands,use_mp=T """ assert int(n_strands) > 2, "The number of strands must be an integer greater than 2" #Construct associated FMatrix object and solve for F-symbols - if not self.fmats.symbols_known: - self.fmats.find_orthogonal_solution(verbose=verbose) + if self.fmats._chkpt_status < 7: + self.fmats.find_orthogonal_solution(checkpoint=checkpoint, + save_results=save_results, + warm_start=warm_start, + use_mp=use_mp, + verbose=verbose) #Set multiprocessing parameters. Context can only be set once, so we try to set it try: diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 45b4f34679c..588278fe217 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -352,31 +352,6 @@ cpdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFie cdef dict gcf_rmvd = remove_gcf(sq_rmvd, nonz) return to_monic(gcf_rmvd, one) -# cdef int common_denom(tuple eq_tup): -# #Compute the common denominator -# cdef list denoms = list() -# cdef int common_denom -# cdef ETuple exp -# for exp, c in eq_tup: -# denoms.append(c.denominator()) -# return LCM_list(denoms) -# -# cdef tuple integralify(tuple eq_tup): -# if not eq_tup: return tuple() -# cdef list ret = list() -# cdef int cd = common_denom(eq_tup) -# for exp, c in eq_tup: -# ret.append((exp, c * cd)) -# return tuple(ret) -# -# cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq): -# """ -# Return a dictionary describing a monic polynomial with no known nonzero gcd and -# no known squares -# """ -# if not eq_dict: return tuple() -# return integralify(to_monic(remove_gcf(subs_squares(eq_dict, known_sq), nonz))) - #################### ### Substitution ### #################### From 3202a1fd6d1de6687efe4de6d737626219d9839a Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Wed, 24 Mar 2021 16:07:06 -0700 Subject: [PATCH 029/414] correct BaKi2001 reference in f_matrix.py --- src/sage/combinat/root_system/f_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 9df09e88ab7..a183231d3d5 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -48,7 +48,7 @@ class FMatrix(): - ``FR`` -- a FusionRing. The :class:`FusionRing` or Verlinde algebra is the - Grothendieck ring of a modular tensor category [Baki2001]_. + Grothendieck ring of a modular tensor category [BaKi2001]_. Such categories arise in conformal field theory or in the representation theories of affine Lie algebras, or quantum groups at roots of unity. They have applications From 5559e96a70e49f2aa32bfe8e654e26e419c7c044 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Mar 2021 08:33:28 -0700 Subject: [PATCH 030/414] changes to avoid segfault form #30537 --- src/sage/combinat/root_system/f_matrix.py | 22 +++-- .../fast_parallel_fmats_methods.pyx | 24 +++++- .../fast_parallel_fusion_ring_braid_repn.pxd | 4 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 81 ++++++++++++++++--- src/sage/combinat/root_system/fusion_ring.py | 22 +++-- .../combinat/root_system/poly_tup_engine.pxd | 4 + .../combinat/root_system/poly_tup_engine.pyx | 24 ++++++ 7 files changed, 157 insertions(+), 24 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index a183231d3d5..d7c52a2db10 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -34,7 +34,7 @@ from sage.combinat.root_system.fast_parallel_fmats_methods import * from sage.combinat.root_system.poly_tup_engine import * #Import faster unsafe method (not for client use) -from sage.combinat.root_system.poly_tup_engine import _tup_to_poly +from sage.combinat.root_system.poly_tup_engine import _tup_to_poly, _unflatten_coeffs from sage.rings.polynomial.polydict import ETuple from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics from sage.rings.real_double import RDF @@ -48,7 +48,7 @@ class FMatrix(): - ``FR`` -- a FusionRing. The :class:`FusionRing` or Verlinde algebra is the - Grothendieck ring of a modular tensor category [BaKi2001]_. + Grothendieck ring of a modular tensor category [BaKi2001]_. Such categories arise in conformal field theory or in the representation theories of affine Lie algebras, or quantum groups at roots of unity. They have applications @@ -187,9 +187,10 @@ class FMatrix(): EXAMPLES:: sage: f.get_defining_equations("pentagons")[1:3] - [fx1*fx5 - fx7^2, fx5*fx8*fx13 - fx2*fx12] + [fx9*fx12 - fx2*fx13, fx3*fx8 - fx4*fx9] sage: f.get_defining_equations("hexagons")[1:3] - [fx10*fx12 + (-zeta128^32)*fx12*fx13 + (-zeta128^16)*fx12, fx0 - 1] + [fx11*fx12 + (-zeta128^32)*fx13^2 + (-zeta128^48)*fx13, + fx10*fx11 + (-zeta128^32)*fx11*fx13 + (-zeta128^16)*fx11] sage: f.get_orthogonality_constraints()[1:3] [fx1^2 - 1, fx2^2 - 1] @@ -672,9 +673,10 @@ def _get_known_sq(self,eqns=None): if eqns is None: eqns = self.ideal_basis ks = deepcopy(self._ks) + F = self._field for eq_tup in eqns: if tup_fixes_sq(eq_tup): - ks[variables(eq_tup)[0]] = -eq_tup[-1][1] + ks[variables(eq_tup)[0]] = -F(list(eq_tup[-1][1])) return ks def _get_known_nonz(self): @@ -941,6 +943,9 @@ def get_defining_equations(self,option,worker_pool=None,output=True): params = [(child_id, n_proc) for child_id in range(n_proc)] eqns = self._map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) if output: + F = self._field + for i, eq_tup in enumerate(eqns): + eqns[i] = _unflatten_coeffs(F, eq_tup) return [self._tup_to_fpoly(p) for p in eqns] self.ideal_basis.extend(eqns) @@ -970,8 +975,15 @@ def _solve_for_linear_terms(self,eqns=None): if eqns is None: eqns = self.ideal_basis + F = self._field linear_terms_exist = False for eq_tup in eqns: + + #Only unflatten relevant polynomials + if len(eq_tup) > 2: + continue + eq_tup = _unflatten_coeffs(F, eq_tup) + if len(eq_tup) == 1: m = eq_tup[0][0] if self._is_univariate_in_unknown(m): diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 2e7c31fe9a2..9b8e013e93e 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -156,6 +156,10 @@ cpdef get_reduced_hexagons(factory, tuple mp_params): he = req_cy(basis,r_matrix,fvars,_Nk_ij,id_anyon,sextuple) if he: red = reduce_poly_dict(he.dict(),_nnz,_ks,one) + + #Avoid pickling cyclotomic coefficients + red = _flatten_coeffs(red) + worker_results.append(red) cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, id_anyon, zero, tuple nonuple, bint prune=False): @@ -202,6 +206,10 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): pe = feq_cy(basis,fvars,_Nk_ij,id_anyon,zero,nonuple,prune=prune) if pe: red = reduce_poly_dict(pe.dict(),_nnz,_ks,one) + + #Avoid pickling cyclotomic coefficients + red = _flatten_coeffs(red) + worker_results.append(red) cpdef update_reduce(factory, tuple eq_tup): @@ -209,9 +217,17 @@ cpdef update_reduce(factory, tuple eq_tup): Substitute known values, known squares, and reduce! """ global worker_results - cdef NumberFieldElement_absolute one =factory._field.one() + cdef NumberFieldElement_absolute one = factory._field.one() + + #Construct cyclotomic field elts from list repn + eq_tup = _unflatten_coeffs(factory._field, eq_tup) + cdef dict eq_dict = subs(eq_tup,factory._kp,one) cdef tuple red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks,one) + + #Avoid pickling cyclotomic coefficients + red = _flatten_coeffs(red) + worker_results.append(red) cpdef compute_gb(factory, tuple args): @@ -231,8 +247,10 @@ cpdef compute_gb(factory, tuple args): #Zip tuples into R and compute Groebner basis cdef idx_map = { old : new for new, old in enumerate(sorted_vars) } nvars = len(sorted_vars) + F = factory.field() cdef list polys = list() for eq_tup in eqns: + eq_tup = _unflatten_coeffs(F, eq_tup) polys.append(tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R)) gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") @@ -242,6 +260,10 @@ cpdef compute_gb(factory, tuple args): nvars = factory._poly_ring.ngens() for p in gb: t = resize(poly_to_tup(p),inv_idx_map,nvars) + + #Avoid pickling cyclotomic coefficients + t = _flatten_coeffs(t) + worker_results.append(t) cpdef update_child_fmats(factory, tuple data_tup): diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd index 7f4c49b63cd..134b6b57f2d 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd @@ -1,5 +1,3 @@ -cpdef mid_sig_ij(fusion_ring,row,col,a,b) -cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b) - cpdef sig_2k(fusion_ring, tuple args) cpdef odd_one_out(fusion_ring, tuple args) +cpdef _unflatten_entries(factory, list entries) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 4e587f1955f..0cb4bd2833a 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -12,8 +12,10 @@ cimport cython import ctypes from itertools import product import sage +from sage.combinat.root_system.poly_tup_engine cimport _flatten_coeffs, _unflatten_coeffs, poly_to_tup from sage.combinat.root_system.fast_parallel_fmats_methods cimport _fmat from sage.misc.cachefunc import cached_function +from sage.rings.qqbar import QQbar #Define a global temporary worker results repository worker_results = list() @@ -72,7 +74,8 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j of trees b -> xj # yj -> (a # a) # (a # a) - ..warning: + .. WARNING:: + This method assumes F-matrices are orthogonal EXAMPLES:: @@ -112,7 +115,8 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): strands, corresponding to the tree b -> (xi # a) -> (a # a) # a, which results in a sum over j of trees b -> xj -> (a # a) # (a # a) - ..warning: + .. WARNING:: + This method assumes F-matrices are orthogonal EXAMPLES:: @@ -145,7 +149,7 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): mid_sig_ij = cached_function(mid_sig_ij, name='mid_sig_ij') odd_one_out_ij = cached_function(odd_one_out_ij, name='odd_one_out_ij') -@cython.wraparound(False) +#@cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) cpdef sig_2k(fusion_ring, tuple args): @@ -165,8 +169,10 @@ cpdef sig_2k(fusion_ring, tuple args): cdef list comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) cdef dict basis_dict = { elt : i for i, elt in enumerate(comp_basis) } cdef int dim = len(comp_basis) - cdef set entries = set() + cdef set coords = set() cdef int i + #Avoid pickling cyclotomic field element objects + must_flatten_coeff = fusion_ring.fvars_field() != QQbar for i in range(dim): for f,e,q in product(fusion_ring.basis(),repeat=3): #Distribute work amongst processes @@ -183,7 +189,7 @@ cpdef sig_2k(fusion_ring, tuple args): nnz_pos = tuple(nnz_pos) #Skip repeated entries when k = 1 - if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in entries: + if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in coords: m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] #A few special cases top_left = m[0] @@ -196,7 +202,13 @@ cpdef sig_2k(fusion_ring, tuple args): #Handle the special case k = 1 if k == 1: entry = mid_sig_ij(fusion_ring,m[:2],(f,e),a,root) + + #Avoid pickling cyclotomic field element objects + if must_flatten_coeff: + entry = _flatten_entry(fusion_ring, entry) + worker_results.append(((basis_dict[nnz_pos],i), entry)) + coords.add((basis_dict[nnz_pos],i)) continue entry = 0 @@ -204,10 +216,18 @@ cpdef sig_2k(fusion_ring, tuple args): f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[k-1],m[k],root,l[k-2],p) f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,e,root,q,p) entry += f1 * mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 + + #Avoid pickling cyclotomic field element objects + if must_flatten_coeff: + #The entry is either a polynomial or a base field element + if entry.parent() == fusion_ring.fmats._poly_ring: + entry = _flatten_coeffs(poly_to_tup(entry)) + else: + entry = entry.list() + worker_results.append(((basis_dict[nnz_pos],i), entry)) - entries.add((basis_dict[nnz_pos],i)) -@cython.wraparound(False) +#@cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) cpdef odd_one_out(fusion_ring, tuple args): @@ -228,6 +248,9 @@ cpdef odd_one_out(fusion_ring, tuple args): comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) basis_dict = { elt : i for i, elt in enumerate(comp_basis) } dim = len(comp_basis) + + #Avoid pickling cyclotomic field element objects + must_flatten_coeff = fusion_ring.fvars_field() != QQbar for i in range(dim): for f, q in product(fusion_ring.basis(),repeat=2): #Distribute work amongst processes @@ -249,6 +272,15 @@ cpdef odd_one_out(fusion_ring, tuple args): #Handle a couple of small special cases if n_strands == 3: entry = odd_one_out_ij(fusion_ring,m[-1],f,a,b) + + #Avoid pickling cyclotomic field element objects + if must_flatten_coeff: + #The entry is either a polynomial or a base field element + if entry.parent() == fusion_ring.fmats._poly_ring: + entry = _flatten_coeffs(poly_to_tup(entry)) + else: + entry = entry.list() + worker_results.append(((basis_dict[nnz_pos],i), entry)) continue top_left = m[0] @@ -262,6 +294,11 @@ cpdef odd_one_out(fusion_ring, tuple args): f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[-1],a,root,l[-1],p) f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,a,root,q,p) entry += f1 * odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 + + #Avoid pickling cyclotomic field element objects + if must_flatten_coeff: + entry = _flatten_entry(fusion_ring, entry) + worker_results.append(((basis_dict[nnz_pos],i), entry)) ################ @@ -275,6 +312,32 @@ def collect_results(proc): """ #Discard the zero polynomial global worker_results - reduced = set(worker_results)-set([tuple()]) + reduced = worker_results #set(worker_results)-set([tuple()]) worker_results = list() - return list(reduced) + return reduced + +###################################### +### Pickling circumvention helpers ### +###################################### + +cdef _flatten_entry(fusion_ring, entry): + #The entry is either a polynomial or a base field element + if entry.parent() == fusion_ring.fmats._poly_ring: + entry = _flatten_coeffs(poly_to_tup(entry)) + else: + entry = entry.list() + return entry + +cpdef _unflatten_entries(factory, list entries): + F = factory.fvars_field() + fm = factory.fmats + must_unflatten = F != QQbar + if must_unflatten: + for i, (coord, entry) in enumerate(entries): + #In this case entry represents a polynomial + if type(entry) == type(tuple()): + entry = fm.tup_to_fpoly(_unflatten_coeffs(F,entry)) + #Otherwise entry belongs to base field + else: + entry = F(entry) + entries[i] = (coord, entry) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 7d6b7aff8ba..e0d62ac0d58 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -15,7 +15,7 @@ from multiprocessing import Pool, set_start_method from sage.combinat.q_analogues import q_int import sage.combinat.root_system.f_matrix as FMatrix -from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results, executor +from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results, executor, _unflatten_entries from sage.combinat.root_system.weyl_characters import WeylCharacterRing from sage.matrix.constructor import matrix from sage.matrix.special import diagonal_matrix @@ -25,6 +25,8 @@ from sage.rings.integer_ring import ZZ from sage.rings.number_field.number_field import CyclotomicField +from sage.rings.qqbar import QQbar + class FusionRing(WeylCharacterRing): r""" Return the Fusion Ring (Verlinde Algebra) of level ``k``. @@ -1138,10 +1140,10 @@ def _emap(self,mapper,input_args,worker_pool=None): if no_mp: results = collect_results(0) else: - results = set() + results = list() for worker_results in worker_pool.imap_unordered(collect_results,range(worker_pool._processes),chunksize=1): - results.update(worker_results) - results = list(results) + results.extend(worker_results) + # results = list(results) return results def get_braid_generators(self, @@ -1235,7 +1237,8 @@ def get_braid_generators(self, set_start_method('fork') except RuntimeError: pass - pool = Pool() if use_mp else None + #Turn off multiprocessing when field is QQbar due to pickling issues introduced by PARI upgrade in trac ticket #30537 + pool = Pool() if use_mp and self.fvars_field() != QQbar else None #Set up computational basis and compute generators one at a time a, b = fusing_anyon, total_charge_anyon @@ -1250,11 +1253,19 @@ def get_braid_generators(self, #Compute even-indexed generators using F-matrices for k in range(1,n_strands//2): entries = self._emap('sig_2k',(k,a,b,n_strands),pool) + + #Build cyclotomic field element objects from tuple of rationals repn + _unflatten_entries(self, entries) + gens[2*k] = matrix(dict(entries)) #If n_strands is odd, we compute the final generator if n_strands % 2: entries = self._emap('odd_one_out',(a,b,n_strands),pool) + + #Build cyclotomic field element objects from tuple of rationals repn + _unflatten_entries(self, entries) + gens[n_strands-1] = matrix(dict(entries)) return comp_basis, [gens[k] for k in sorted(gens)] @@ -1451,4 +1462,3 @@ def q_dimension(self, base_coercion=True): if (not base_coercion) or (self.parent()._basecoer is None): return ret return self.parent()._basecoer(ret) - diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 5f614ae69de..5eb7e11ad18 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -18,3 +18,7 @@ cpdef tuple poly_tup_sortkey_degrevlex(tuple eq_tup) cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one) + + +cdef tuple _flatten_coeffs(tuple eq_tup) +cpdef tuple _unflatten_coeffs(field, tuple eq_tup) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 588278fe217..2e6dec0db4a 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -84,7 +84,31 @@ cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsing """ return parent._element_constructor_(dict(eq_tup), check=False) +cdef tuple _flatten_coeffs(tuple eq_tup): + """ + Flatten cyclotomic coefficients to a representation as a tuple of rational + coefficients. + + This is used to avoid pickling cyclotomic coefficient objects, which fails + with new PARI settings introduced in trac ticket #30537 + """ + cdef list flat = list() + for exp, cyc_coeff in eq_tup: + flat.append((exp, tuple(cyc_coeff._coefficients()))) + return tuple(flat) +cpdef tuple _unflatten_coeffs(field, tuple eq_tup): + """ + Restore cyclotomic coefficient object from its tuple of rational + coefficients representation. + + Used to circumvent pickling issue introduced by PARI settigs in trac + ticket #30537 + """ + cdef list unflat = list() + for exp, coeff_tup in eq_tup: + unflat.append((exp, field(list(coeff_tup)))) + return tuple(unflat) ###################### ### "Change rings" ### From 3429f2561ac0ae71ae626971d93ff3a47a9d3db3 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 26 Mar 2021 16:02:12 -0700 Subject: [PATCH 031/414] docstring revisions --- src/sage/combinat/root_system/f_matrix.py | 169 +++++++++++------- .../fast_parallel_fusion_ring_braid_repn.pyx | 37 +++- src/sage/combinat/root_system/fusion_ring.py | 8 +- .../combinat/root_system/poly_tup_engine.pyx | 13 ++ 4 files changed, 156 insertions(+), 71 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index d7c52a2db10..2e78fc0e108 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -41,11 +41,11 @@ class FMatrix(): - r"""Return an F-Matrix factory for a FusionRing. + r"""Return an F-Matrix factory for a :class:`FusionRing`. INPUT: - - ``FR`` -- a FusionRing. + - ``FR`` -- a :class:`FusionRing`. The :class:`FusionRing` or Verlinde algebra is the Grothendieck ring of a modular tensor category [BaKi2001]_. @@ -54,13 +54,13 @@ class FMatrix(): quantum groups at roots of unity. They have applications to low dimensional topology and knot theory, to conformal field theory and to topological quantum computing. The - FusionRing captures much information about a fusion category, - but to complete the picture, the F-matrices or 6j-symbols - are needed. For example these are required in order to - construct braid group representations. + :class:`FusionRing` captures much information about a fusion + category, but to complete the picture, the F-matrices or + 6j-symbols are needed. For example these are required in + order to construct braid group representations. We only undertake to compute the F-matrix if the - FusionRing is *multiplicity free* meaning that + :class:`FusionRing` is *multiplicity free* meaning that the Fusion coefficients `N^{ij}_k` are bounded by 1. For Cartan Types `X_r` and level `k`, the multiplicity-free cases are given by the @@ -87,10 +87,11 @@ class FMatrix(): Beyond this limitation, computation of the F-matrix can involve very large systems of equations. A rule of thumb is that this code can compute the - F-matrix for systems with `\leq 4` primary fields, - with the exception of `G_2` at level `2`. + F-matrix for systems with `\leq 14` primary fields + (simple objects), on a machine with 16 GB of memory. + Larger examples can be quite time consuming. - The FusionRing and its methods capture much + The :class:`FusionRing` and its methods capture much of the structure of the underlying tensor category. But an important aspect that is not encoded in the fusion ring is the associator, which is a homomorphism @@ -202,10 +203,10 @@ class FMatrix(): that should be kept in mind. :meth:`find_cyclotomic_solution` currently works only with - smaller examples. For example the FusionRing for A2 at + smaller examples. For example the :class:`FusionRing` for A2 at level 2 is too large. When it is available, this method produces an F-matrix whose entries are in the same - cyclotomic field as the underlying FusionRing. + cyclotomic field as the underlying :class:`FusionRing`. EXAMPLES:: @@ -234,18 +235,24 @@ class FMatrix(): quickly but sometimes (in larger cases) after hours of computation. Its F-matrices are not always in the cyclotomic field that is the base ring of the underlying - FusionRing, but sometimes in an extension field adjoining - some square roots. When this happens, the FusionRing is + :class:`FusionRing`, but sometimes in an extension field adjoining + some square roots. When this happens, the :class:`FusionRing` is modified, adding an attribute :attr:`_basecoer` that is - a coercion from the cyclotomic field to the F-matrix. + a coercion from the cyclotomic field to the field + containing the F-matrix. The field containing the F-matrix + is available through :meth:`field`. EXAMPLES:: sage: f = FMatrix(FusionRing("B3",2)) - sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # long time + sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # long time (~100 s) sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # long time True + sage: f = FMatrix(FusionRing("G2",2)) + sage: f.find_orthogonal_solution(verbose=False) # long time (~18 s) + sage: f.field() # long time + Algebraic Field """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): self._FR = fusion_ring @@ -312,7 +319,8 @@ def clear_vars(self): def _reset_solver_state(self): """ - Reset solver state and clear relevant cache. + Reset solver state and clear relevant cache. Used to ensure + state variables are the same for each orthogonal solver run. """ self._FR._basecoer = None self._field = self._FR.field() @@ -402,7 +410,7 @@ def fmatrix(self,a,b,c,d): INPUT: - - ``a,b,c,d`` -- basis elements of the FusionRing + - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing` EXAMPLES:: @@ -429,7 +437,7 @@ def fmatrix(self,a,b,c,d): def field(self): r""" Return the base field containing the F-symbols. When ``self`` is initialized, - the field is set to be the cyclotomic field of the FusionRing associated + the field is set to be the cyclotomic field of the :class:`FusionRing` associated to ``self``. The field may change after running :meth:`find_orthogonal_solution`. EXAMPLES:: @@ -445,7 +453,7 @@ def field(self): def FR(self): r""" - Return the FusionRing associated to ``self``. + Return the :class:`FusionRing` associated to ``self``. EXAMPLES:: @@ -501,7 +509,14 @@ def findcases(self,output=False): def singletons(self): r""" Find `x_i` that are automatically nonzero, because their F-matrix is - `1 \\times 1`. + `1 \times 1`. + + EXAMPLES:: + + sage: fm = FMatrix(FusionRing("E7", 1)) + sage: singles = fm.singletons() + sage: all(fm.fmatrix(*fm._var_to_sextuple[v][:4]).nrows() == 1 for v in singles) + True """ ret = [] for (a, b, c, d) in list(product(self._FR.basis(), repeat=4)): @@ -519,7 +534,7 @@ def f_from(self,a,b,c,d): INPUT: - - ``a,b,c,d`` -- basis elements of the FusionRing. + - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing`. EXAMPLES:: @@ -543,7 +558,7 @@ def f_to(self,a,b,c,d): INPUT: - - ``a,b,c,d`` -- basis elements of the FusionRing. + - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing`. EXAMPLES:: @@ -591,7 +606,8 @@ def get_fvars(self): def get_non_cyclotomic_roots(self): r""" Return a list of roots that define the extension of the associated - ``FusionRing``'s base ``CyclotomicField`` containing all the F-symbols. + :class:`FusionRing`'s base :class:`CyclotomicField` containing all + the F-symbols. OUTPUT: @@ -601,7 +617,7 @@ def get_non_cyclotomic_roots(self): If ``self.field() == self.FR().field()`` this method returns an empty list. When ``self.field()`` is a ``NumberField``, one may use - :meth:``get_qqbar_embedding`` to embed the resulting values into ``QQbar``. + :meth:`get_qqbar_embedding` to embed the resulting values into ``QQbar``. EXAMPLES:: @@ -619,16 +635,32 @@ def get_non_cyclotomic_roots(self): def get_qqbar_embedding(self): r""" Return an embedding from the base field containing F-symbols (the - ``FusionRing``'s ``CyclotomicField``, a ``NumberField``, or ``QQbar``) + associated :class:`FusionRing`'s :class:`CyclotomicField`, a + :class:`NumberField`, or ``QQbar``) into ``QQbar``. + + This embedding is useful for getting a better sense for the + F-symbols, particularly when they are computed as elements of a + :class:`NumberField`. + + EXAMPLES:: + + sage: fm = FMatrix(FusionRing("C3",1), fusion_label="c", inject_variables=True) + creating variables fx1..fx71 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26, fx27, fx28, fx29, fx30, fx31, fx32, fx33, fx34, fx35, fx36, fx37, fx38, fx39, fx40, fx41, fx42, fx43, fx44, fx45, fx46, fx47, fx48, fx49, fx50, fx51, fx52, fx53, fx54, fx55, fx56, fx57, fx58, fx59, fx60, fx61, fx62, fx63, fx64, fx65, fx66, fx67, fx68, fx69, fx70 + sage: fm.find_orthogonal_solution(verbose=False) + sage: phi = fm.get_qqbar_embedding() + sage: phi(fm.get_fvars()[c1,c2,c2,c1,c1,c2]) + -0.61803399? + 0.?e-8*I """ return self._qqbar_embedding def get_coerce_map_from_fr_cyclotomic_field(self): r""" - Return a coercion map from the associated ``FusionRing``'s cyclotomic - field into the base field containing all F-symbols (this could be the - ``FusionRing``'s ``CyclotomicField``, a ``NumberField``, or ``QQbar``). + Return a coercion map from the associated :class:`FusionRing`'s + cyclotomic field into the base field containing all F-symbols + (this could be the :class:`FusionRing`'s :class:`CyclotomicField`, a + :class:`NumberField`, or ``QQbar``). """ #If base field is different from associated FusionRing's CyclotomicField, #return coercion map @@ -643,7 +675,7 @@ def get_fmats_in_alg_field(self): r""" Return F-symbols as elements of the :class:`AlgebraicField` ``QQbar``. This method uses the embedding defined by - :meth:``self.get_qqbar_embedding`` to coerce + :meth:`self.get_qqbar_embedding` to coerce F-symbols into ``QQbar``. """ return { sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items() } @@ -651,8 +683,18 @@ def get_fmats_in_alg_field(self): def get_radical_expression(self): """ Return radical expression of F-symbols for easy visualization + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",3), fusion_label="a", inject_variables=True) + creating variables fx1..fx71 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26, fx27, fx28, fx29, fx30, fx31, fx32, fx33, fx34, fx35, fx36, fx37, fx38, fx39, fx40, fx41, fx42, fx43, fx44, fx45, fx46, fx47, fx48, fx49, fx50, fx51, fx52, fx53, fx54, fx55, fx56, fx57, fx58, fx59, fx60, fx61, fx62, fx63, fx64, fx65, fx66, fx67, fx68, fx69, fx70 + sage: f.find_orthogonal_solution(verbose=False) + sage: fvars = f.get_radical_expression() + sage: fvars[a1, a1, a1, a1, a2, a0] + -sqrt(1/2*sqrt(5) - 1/2) """ - return { sextuple : val.radical_expression() for sextuple, val in get_fmats_in_alg_field().items() } + return { sextuple : val.radical_expression() for sextuple, val in self.get_fmats_in_alg_field().items() } ####################### ### Private helpers ### @@ -726,7 +768,7 @@ def largest_fmat_size(self): def get_fvars_by_size(self,n,indices=False): r""" - Return the set of F-symbols that are entries of an `n \\times n` matrix + Return the set of F-symbols that are entries of an `n \times n` matrix `F^{a,b,c}_d`. INPUT: @@ -907,7 +949,7 @@ def get_orthogonality_constraints(self,output=True): polynomial objects. Otherwise, the constraints are appended to ``self.ideal_basis``. - They are stored in the internal tuple representation. The ``output==False`` + They are stored in the internal tuple representation. The ``output=False`` option is meant mostly for internal use by the F-matrix solver. """ eqns = list() @@ -1083,7 +1125,7 @@ def equations_graph(self,eqns=None): a given equation. If no list of equations is passed, the graph is built from equations in - self.ideal_basis + ``self.ideal_basis``. """ if eqns is None: eqns = self.ideal_basis @@ -1127,7 +1169,7 @@ def _add_square_fixers(self,output=False): return sq_fixers self.ideal_basis.extend(sq_fixers) - def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): + def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): """ Compute a Groebner basis for a set of equations partitioned according to their corresponding graph """ @@ -1159,7 +1201,7 @@ def par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose= ret = small_comp_gb + temp_eqns return ret - def get_component_variety(self,var,eqns): + def _get_component_variety(self,var,eqns): """ Translate equations in each connected component to smaller polynomial rings so we can call built-in variety method. @@ -1184,12 +1226,13 @@ def get_component_variety(self,var,eqns): def attempt_number_field_computation(self): """ Based on the ``CartanType`` of ``self``, determine whether to attempt - to find a ``NumberField`` containing all the F-symbols based on data + to find a :class:`NumberField` containing all the F-symbols based on data known on March 17, 2021. - For certain ``FusionRing``s, the number field computation does not - seem to terminate. In these cases, we report F-symbols as elements - of the ``AlgebraicField``. + For certain :class:`FusionRing` 's, the number field computation does + not terminate in a reasonable amount of time. + In these cases, we report F-symbols as elements + of the ``AlgebraicField`` :class:`QQbar`. EXAMPLES:: @@ -1202,11 +1245,12 @@ def attempt_number_field_computation(self): """ ct = self._FR.cartan_type() k = self._FR._k + #Don't try when k is large and odd for SU(2)_k if ct.letter == 'A': - if ct.n == 1 and k >= 9: + if ct.n == 1 and k >= 9 and k % 2: return False if ct.letter == 'C': - if ct.n >= 9 and k == 1: + if ct.n >= 9 and ct.n % 2 and k == 1: return False if ct.letter == 'E': if ct.n < 8 and k == 2: @@ -1252,7 +1296,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): must_change_base_field = True #Otherwise, compute the component variety and select a point to obtain a numerical solution else: - sols = self.get_component_variety(comp,part) + sols = self._get_component_variety(comp,part) assert len(sols) > 1, "No real solution exists... component with variables {} has no real points".format(comp) for fx, rhs in sols[0].items(): non_cyclotomic_roots.append((fx,rhs)) @@ -1318,14 +1362,14 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" ``"fmatrix_solver_checkpoint_" + key + ".pickle"``, where ``key`` is the result of :meth:`get_fr_str`. - Generated checkpoint pickles are automatically deleted when the - solver runs successfully. + Checkpoint pickles are automatically deleted when the solver exits + a successful run. - ``save_results`` -- (optional) a string indicating the name of a pickle file in which to store calculated F-symbols for later use. - If ``save_results`` is not provided (default), results are not - stored to file. + If ``save_results`` is not provided (default), F-matrix results + are not stored to file. The F-symbols may be saved to file after running the solver using :meth:`save_fvars`. @@ -1349,11 +1393,11 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" :meth:`get_fvars`, :meth:`fmatrix`, :meth:`fmat`, etc. In many cases the F-symbols obtained are in fact real. In any case, the - F-symbols are obtained as elements of the associated ``FusionRing``'s - ``CyclotomicField``, a computed ``NumberField``, or ``QQbar``. - Currently, the output field is determined based on the ``CartanType`` - associated to ``self``. See :meth:`attempt_number_field_computation` - for details. + F-symbols are obtained as elements of the associated + :class:`FusionRing`'s :class:`CyclotomicField`, a computed + :class:`NumberField`, or ``QQbar``. Currently, the output field is + determined based on the ``CartanType`` associated to ``self``. + See :meth:`attempt_number_field_computation` for details. """ self._reset_solver_state() @@ -1386,7 +1430,7 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" if self._chkpt_status < 2: #Set up equations graph. Find GB for each component in parallel. Eliminate variables - self.ideal_basis = self.par_graph_gb(worker_pool=pool,verbose=verbose) + self.ideal_basis = self._par_graph_gb(worker_pool=pool,verbose=verbose) self.ideal_basis.sort(key=poly_sortkey) self._triangular_elim(worker_pool=pool,verbose=verbose) @@ -1428,7 +1472,7 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" #Set up new equations graph and compute variety for each component if self._chkpt_status < 5: - self.ideal_basis = self.par_graph_gb(term_order="lex",verbose=verbose) + self.ideal_basis = self._par_graph_gb(term_order="lex",verbose=verbose) self.ideal_basis.sort(key=poly_sortkey) self._triangular_elim(verbose=verbose) @@ -1452,7 +1496,8 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" def fix_gauge(self, algorithm=''): """ Fix the gauge by forcing F-symbols not already fixed to equal 1. - This method should be used AFTER adding hex and pentagon eqns to ideal_basis + This method should be used AFTER adding hex and pentagon eqns to + ``self.ideal_basis`` """ while len(self._solved) < len(self._poly_ring.gens()): #Get a variable that has not been fixed @@ -1465,10 +1510,10 @@ def fix_gauge(self, algorithm=''): self.ideal_basis.add(var-1) print("adding equation...", var-1) self.ideal_basis = set(Ideal(list(self.ideal_basis)).groebner_basis(algorithm=algorithm)) - self.substitute_degree_one() - self.update_equations() + self._substitute_degree_one() + self._update_equations() - def substitute_degree_one(self, eqns=None): + def _substitute_degree_one(self, eqns=None): if eqns is None: eqns = self.ideal_basis @@ -1492,7 +1537,7 @@ def substitute_degree_one(self, eqns=None): self._fvars[sextuple] = rhs.subs(d) return new_knowns, useless - def update_equations(self): + def _update_equations(self): """ Update ideal_basis equations by plugging in known values """ @@ -1540,8 +1585,8 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o (a1, a1, a2, a1, a2, a0): 1, (a1, a1, a1, a0, a2, a2): 1} - After you successfully run ``get_solution`` you may check - the correctness of the F-matrix by running :meth:`hexagon` + After you successfully run :meth:`find_cyclotomic_solution` you may + check the correctness of the F-matrix by running :meth:`hexagon` and :meth:`pentagon`. These should return empty lists of equations. @@ -1564,7 +1609,7 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o self.ideal_basis = set(Ideal(equations).groebner_basis(algorithm=algorithm)) if verbose: print("Solving...") - self.substitute_degree_one() + self._substitute_degree_one() if verbose: print("Fixing the gauge...") self.fix_gauge(algorithm=algorithm) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 0cb4bd2833a..663249fd952 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -28,7 +28,7 @@ def executor(params): """ Execute a function defined in this module (sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn) - in a worker process, and supply the factory parameter by constructing a + in a worker process, and supply the `FusionRing` parameter by constructing a reference to the FMatrix object in the worker's memory adress space from its id. @@ -149,7 +149,6 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): mid_sig_ij = cached_function(mid_sig_ij, name='mid_sig_ij') odd_one_out_ij = cached_function(odd_one_out_ij, name='odd_one_out_ij') -#@cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) cpdef sig_2k(fusion_ring, tuple args): @@ -227,7 +226,6 @@ cpdef sig_2k(fusion_ring, tuple args): worker_results.append(((basis_dict[nnz_pos],i), entry)) -#@cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) cpdef odd_one_out(fusion_ring, tuple args): @@ -312,7 +310,7 @@ def collect_results(proc): """ #Discard the zero polynomial global worker_results - reduced = worker_results #set(worker_results)-set([tuple()]) + reduced = worker_results worker_results = list() return reduced @@ -321,6 +319,13 @@ def collect_results(proc): ###################################### cdef _flatten_entry(fusion_ring, entry): + """ + Flatten cyclotomic coefficients to a representation as a tuple of rational + coefficients. + + This is used to avoid pickling cyclotomic coefficient objects, which fails + with new PARI settings introduced in trac ticket #30537 + """ #The entry is either a polynomial or a base field element if entry.parent() == fusion_ring.fmats._poly_ring: entry = _flatten_coeffs(poly_to_tup(entry)) @@ -328,9 +333,27 @@ cdef _flatten_entry(fusion_ring, entry): entry = entry.list() return entry -cpdef _unflatten_entries(factory, list entries): - F = factory.fvars_field() - fm = factory.fmats +cpdef _unflatten_entries(fusion_ring, list entries): + """ + Restore cyclotomic coefficient object from its tuple of rational + coefficients representation. + + Used to circumvent pickling issue introduced by PARI settigs in trac + ticket #30537 + + EXAMPLES:: + + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import _unflatten_entries + sage: fr = FusionRing("B2",2) + sage: F = fr.field() + sage: coeff = [F.random_element() for i in range(2)] + sage: entries = [((0,0), coeff[0].list()), ((0,1), coeff[1].list())] + sage: _unflatten_entries(fr, entries) + sage: all(cyc_elt_obj == c for (coord, cyc_elt_obj), c in zip(entries, coeff)) + True + """ + F = fusion_ring.fvars_field() + fm = fusion_ring.fmats must_unflatten = F != QQbar if must_unflatten: for i, (coord, entry) in enumerate(entries): diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index e0d62ac0d58..1f3a11adbbf 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -544,6 +544,10 @@ def fvars_field(self): Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 sage: A13.field() Cyclotomic Field of order 40 and degree 16 + + In some cases, the :meth:`NumberField.optimized_representation` + may be used to obtain a better defining polynomial for the + computed ``NumberField``. """ if self.is_multiplicity_free(): return self.fmats.field() @@ -1275,8 +1279,8 @@ def gens_satisfy_braid_gp_rels(self,sig): Return True if the matrices in the list ``sig`` satisfy the braid relations. This if `n` is the cardinality of ``sig``, this confirms that these matrices define a representation of - the Artin braid group on `n+1` strands.Tests correctness of - get_braid_generators method. + the Artin braid group on `n+1` strands. Tests correctness of + :meth:`get_braid_generators`. EXAMPLES:: diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 2e6dec0db4a..fe4b6bf7799 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -104,6 +104,19 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): Used to circumvent pickling issue introduced by PARI settigs in trac ticket #30537 + + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs + sage: fm = FMatrix(FusionRing("A2",2)) + sage: p = fm._poly_ring.random_element() + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: flat_poly_tup = list() + sage: for exp, cyc_coeff in poly_to_tup(p): + ....: flat_poly_tup.append((exp, tuple(cyc_coeff._coefficients()))) + sage: flat_poly_tup = tuple(flat_poly_tup) + sage: _unflatten_coeffs(fm.field(), flat_poly_tup) == poly_to_tup(p) + True """ cdef list unflat = list() for exp, coeff_tup in eq_tup: From 49e27efd33dc53c9e61d50a3ff210e9f3086ac11 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Mon, 5 Apr 2021 16:15:04 -0700 Subject: [PATCH 032/414] Add CW2015 to index.rst --- src/doc/en/reference/references/index.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 26769808182..7aa2c0abb6f 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1763,6 +1763,9 @@ REFERENCES: .. [CHW2015] Shawn X.; Hong, Seung-Moon; Wang, Zhenghan Universal quantum computation with weakly integral anyons. Quantum Inf. Process. 14 (2015), no. 8, 2687–2727. +.. [CW2015] Cui, S. X. and Wang, Z. (2015). Universal quantum computation with + metaplectic anyons. Journal of Mathematical Physics, 56(3), 032202. + doi:10.1063/1.4914941 .. _ref-D: From c178754d86ae1ec7974e5507d08271abd7f65dd4 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 6 Apr 2021 18:58:38 -0400 Subject: [PATCH 033/414] testing push to trac --- src/sage/combinat/root_system/f_matrix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 2e78fc0e108..694f5544230 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -11,6 +11,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +#Testing git push from itertools import product, zip_longest import sage.combinat.root_system.fusion_ring as FusionRing import sage.graphs From 0b24ef9f7d3ed0131a317efe9eb9e87bc196e54a Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 6 Apr 2021 19:19:59 -0400 Subject: [PATCH 034/414] faster doctests, full coverage --- src/sage/combinat/root_system/f_matrix.py | 1795 ++++++++++++----- .../fast_parallel_fmats_methods.pxd | 6 +- .../fast_parallel_fmats_methods.pyx | 417 +++- .../fast_parallel_fusion_ring_braid_repn.pxd | 4 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 230 ++- src/sage/combinat/root_system/fusion_ring.py | 221 +- .../combinat/root_system/poly_tup_engine.pxd | 21 +- .../combinat/root_system/poly_tup_engine.pyx | 421 ++-- 8 files changed, 2106 insertions(+), 1009 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 694f5544230..a7d276c2a0d 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -11,251 +11,268 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -#Testing git push -from itertools import product, zip_longest -import sage.combinat.root_system.fusion_ring as FusionRing -import sage.graphs -from sage.graphs.generators.basic import EmptyGraph -from sage.matrix.constructor import matrix -from sage.misc.misc import inject_variable -from sage.rings.polynomial.all import PolynomialRing -from sage.rings.ideal import Ideal -from sage.misc.misc import get_main_globals -from copy import deepcopy -#Import pickle for checkpointing and loading certain variables +#Import pickle for checkpointing and loading try: import cPickle as pickle except: import pickle - +from copy import deepcopy +from itertools import product, zip_longest from multiprocessing import cpu_count, Pool, set_start_method import numpy as np import os -from sage.combinat.root_system.fast_parallel_fmats_methods import * -from sage.combinat.root_system.poly_tup_engine import * -#Import faster unsafe method (not for client use) -from sage.combinat.root_system.poly_tup_engine import _tup_to_poly, _unflatten_coeffs + +from sage.combinat.root_system.fast_parallel_fmats_methods import ( + _backward_subs, _solve_for_linear_terms, + collect_eqns, executor +) +from sage.combinat.root_system.poly_tup_engine import ( + apply_coeff_map, constant_coeff, + compute_known_powers, + get_variables_degrees, variables, + poly_to_tup, _tup_to_poly, tup_to_univ_poly, + _unflatten_coeffs, + poly_tup_sortkey, + tup_fixes_sq, + resize, +) +from sage.graphs.graph import Graph +from sage.matrix.constructor import matrix +from sage.misc.misc import get_main_globals +from sage.rings.ideal import Ideal +from sage.rings.polynomial.all import PolynomialRing from sage.rings.polynomial.polydict import ETuple from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics from sage.rings.real_double import RDF - class FMatrix(): - r"""Return an F-Matrix factory for a :class:`FusionRing`. - - INPUT: - - - ``FR`` -- a :class:`FusionRing`. - - The :class:`FusionRing` or Verlinde algebra is the - Grothendieck ring of a modular tensor category [BaKi2001]_. - Such categories arise in conformal field theory or in the - representation theories of affine Lie algebras, or - quantum groups at roots of unity. They have applications - to low dimensional topology and knot theory, to conformal - field theory and to topological quantum computing. The - :class:`FusionRing` captures much information about a fusion - category, but to complete the picture, the F-matrices or - 6j-symbols are needed. For example these are required in - order to construct braid group representations. - - We only undertake to compute the F-matrix if the - :class:`FusionRing` is *multiplicity free* meaning that - the Fusion coefficients `N^{ij}_k` are bounded - by 1. For Cartan Types `X_r` and level `k`, - the multiplicity-free cases are given by the - following table. - -+------------------------+----------+ -| Cartan Type | `k` | -+========================+==========+ -| `A_1` | any | -+------------------------+----------+ -| `A_r, r\geq 2` | `\leq 2` | -+------------------------+----------+ -| `B_r, r\geq 2` | `\leq 2` | -+------------------------+----------+ -| `C_2` | `\leq 2` | -+------------------------+----------+ -| `C_r, r\geq 3` | `\leq 1` | -+------------------------+----------+ -| `D_r, r\geq 4` | `\leq 2` | -+------------------------+----------+ -| `G_2,F_4,E_r` | `\leq 2` | -+------------------------+----------+ - - Beyond this limitation, computation of the F-matrix - can involve very large systems of equations. A - rule of thumb is that this code can compute the - F-matrix for systems with `\leq 14` primary fields - (simple objects), on a machine with 16 GB of memory. - Larger examples can be quite time consuming. - - The :class:`FusionRing` and its methods capture much - of the structure of the underlying tensor category. - But an important aspect that is not encoded in the - fusion ring is the associator, which is a homomorphism - `(A\otimes B)\otimes C\to A\otimes(B\otimes C)` - requires an additional tool, the F-matrix or 6j-symbol. - To specify this, we fix a simple object `D` - and represent the transformation - - .. MATH:: - - \text{Hom}(D,(A\otimes B)\otimes C) \to \text{Hom}(D,A\otimes(B\otimes C)) - - by a matrix `F^{ABC}_D`. This depends on a pair of - additional simple objects `X` and `Y`. Indeed, we can - get a basis for `\text{Hom}(D,(A\otimes B)\otimes C)` - indexed by simple objects `X` in which the corresponding - homomorphism factors through `X\otimes C`, and similarly - `\text{Hom}(D,A\otimes(B\otimes C))` has a basis indexed - by `Y`, in which the basis vector factors through `A\otimes Y`. - - See [TTWL2009]_ for an introduction to this topic, - [EGNO2015]_ Section 4.9 for a precise mathematical - definition and [Bond2007]_ Section 2.5 for a discussion - of how to compute the F-matrix. In addition to - [Bond2007]_ worked out F-matrices may be found in - [RoStWa2009]_ and [CHW2015]_. - - The F-matrix is only determined up to a *gauge*. This - is a family of embeddings `C\to A\otimes B` for - simple objects `A,B,C` such that `\text{Hom}(C,A\otimes B)` - is nonzero. Changing the gauge changes the F-matrix though - not in a very essential way. By varying the gauge it is - possible to make the F-matrices unitary, or it is possible - to make them cyclotomic. - - Due to the large number of equations we may fail to find a - Groebner basis if there are too many variables. - - - EXAMPLES:: - - sage: I=FusionRing("E8",2,conjugate=True) - sage: I.fusion_labels(["i0","p","s"],inject_variables=True) - sage: f = FMatrix(I,inject_variables=True); f - creating variables fx1..fx14 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients - - We've exported two sets of variables to the global namespace. - We created three variables ``i0, p, s`` to represent the - primary fields (simple elements) of the FusionRing. Creating - the FMatrix factory also created variables ``fx1,fx2, ... , fx14`` - in order to solve the hexagon and pentagon equations describing - the F-matrix. Since we called ``FMatrix`` with the parameter ``inject_variables`` - set true, these have been exported into the global namespace. This - is not necessary for the code to work but if you want to - run the code experimentally you may want access to these - variables. - - EXAMPLES:: - - sage: f.fmatrix(s,s,s,s) - [fx10 fx11] - [fx12 fx13] - - The F-matrix has not been computed at this stage, so - the F-matrix `F^{sss}_s` is filled with variables - ``fx10``, ``fx11``, ``fx12``, ``fx13``. The task is - to solve for these. - - As explained above The F-matrix `(F^{ABC}_D)_{X,Y}` - two other variables `X` and `Y`. We have methods to - tell us (depending on `A,B,C,D`) what the possibilities - for these are. In this example with `A=B=C=D=s` - both `X` and `Y` are allowed to be `i_0` or `s`. - - EXAMPLES:: - - sage: f.f_from(s,s,s,s), f.f_to(s,s,s,s) - ([i0, p], [i0, p]) - - The last two statments show that the possible values of - `X` and `Y` when `A=B=C=D=s` are `i_0` and `p`. - - The F-matrix is computed by solving the so-called - pentagon and hexagon equations. The *pentagon - equations* reflect the Mac Lane pentagon axiom in the - definition of a monoidal category. The hexagon relations - reflect the axioms of a *braided monoidal category*, - which are constraints on both the F-matrix and on - the R-matrix. Optionally, orthogonality constraints - may be imposed to obtain an orthogonal F-matrix. - - EXAMPLES:: - - sage: f.get_defining_equations("pentagons")[1:3] - [fx9*fx12 - fx2*fx13, fx3*fx8 - fx4*fx9] - sage: f.get_defining_equations("hexagons")[1:3] - [fx11*fx12 + (-zeta128^32)*fx13^2 + (-zeta128^48)*fx13, - fx10*fx11 + (-zeta128^32)*fx11*fx13 + (-zeta128^16)*fx11] - sage: f.get_orthogonality_constraints()[1:3] - [fx1^2 - 1, fx2^2 - 1] - - There are two methods available to compute an F-matrix. - The first, :meth:`find_cyclotomic_solution` uses only - the pentagon and hexagon relations. The second, - :meth:`find_orthogonal_solution` uses additionally - the orthogonality relations. There are some differences - that should be kept in mind. - - :meth:`find_cyclotomic_solution` currently works only with - smaller examples. For example the :class:`FusionRing` for A2 at - level 2 is too large. When it is available, this method - produces an F-matrix whose entries are in the same - cyclotomic field as the underlying :class:`FusionRing`. - - EXAMPLES:: - - sage: f.find_cyclotomic_solution() - Setting up hexagons and pentagons... - Finding a Groebner basis... - Solving... - Fixing the gauge... - adding equation... fx1 - 1 - adding equation... fx11 - 1 - Done! - - We now have access to the values of the F-mstrix using - the methods :meth:`fmatrix` and :meth:`fmat`. - - EXAMPLES:: - - sage: f.fmatrix(s,s,s,s) - [(-1/2*zeta128^48 + 1/2*zeta128^16) 1] - [ 1/2 (1/2*zeta128^48 - 1/2*zeta128^16)] - sage: f.fmat(s,s,s,s,p,p) - (1/2*zeta128^48 - 1/2*zeta128^16) - - :meth:`find_orthogonal_solution` is much more powerful - and is capable of handling large cases, sometimes - quickly but sometimes (in larger cases) after hours of - computation. Its F-matrices are not always in the - cyclotomic field that is the base ring of the underlying - :class:`FusionRing`, but sometimes in an extension field adjoining - some square roots. When this happens, the :class:`FusionRing` is - modified, adding an attribute :attr:`_basecoer` that is - a coercion from the cyclotomic field to the field - containing the F-matrix. The field containing the F-matrix - is available through :meth:`field`. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("B3",2)) - sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # long time (~100 s) - sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # long time - True - - sage: f = FMatrix(FusionRing("G2",2)) - sage: f.find_orthogonal_solution(verbose=False) # long time (~18 s) - sage: f.field() # long time - Algebraic Field - """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): + r"""Return an F-Matrix factory for a :class:`FusionRing`. + + INPUT: + + - ``FR`` -- a :class:`FusionRing`. + + - ``fusion_label`` -- (optional) a string used to label basis elements + of the :class:`FusionRing` associated to ``self``. + + See :meth:`FusionRing.fusion_labels`. + + - ``var_prefix`` -- (optional) a string indicating the desired prefix + for variables denoting F-symbols to be solved. + + - ``inject_variables`` -- (default: ``False``) a boolean indicating + whether to inject variables (:class:`FusionRing` basis element + labels and F-symbols) into the global namespace. + + The :class:`FusionRing` or Verlinde algebra is the + Grothendieck ring of a modular tensor category [BaKi2001]_. + Such categories arise in conformal field theory or in the + representation theories of affine Lie algebras, or + quantum groups at roots of unity. They have applications + to low dimensional topology and knot theory, to conformal + field theory and to topological quantum computing. The + :class:`FusionRing` captures much information about a fusion + category, but to complete the picture, the F-matrices or + 6j-symbols are needed. For example these are required in + order to construct braid group representations. + + We only undertake to compute the F-matrix if the + :class:`FusionRing` is *multiplicity free* meaning that + the Fusion coefficients `N^{ij}_k` are bounded + by 1. For Cartan Types `X_r` and level `k`, + the multiplicity-free cases are given by the + following table. + + +------------------------+----------+ + | Cartan Type | `k` | + +========================+==========+ + | `A_1` | any | + +------------------------+----------+ + | `A_r, r\geq 2` | `\leq 2` | + +------------------------+----------+ + | `B_r, r\geq 2` | `\leq 2` | + +------------------------+----------+ + | `C_2` | `\leq 2` | + +------------------------+----------+ + | `C_r, r\geq 3` | `\leq 1` | + +------------------------+----------+ + | `D_r, r\geq 4` | `\leq 2` | + +------------------------+----------+ + | `G_2,F_4,E_r` | `\leq 2` | + +------------------------+----------+ + + Beyond this limitation, computation of the F-matrix + can involve very large systems of equations. A + rule of thumb is that this code can compute the + F-matrix for systems with `\leq 14` simple objects + (primary fields) on a machine with 16 GB of memory. + (Larger examples can be quite time consuming.) + + The :class:`FusionRing` and its methods capture much + of the structure of the underlying tensor category. + But an important aspect that is not encoded in the + fusion ring is the associator, which is a homomorphism + `(A\otimes B)\otimes C\to A\otimes(B\otimes C)` that + requires an additional tool, the F-matrix or 6j-symbol. + To specify this, we fix a simple object `D` + and represent the transformation + + .. MATH:: + + \text{Hom}(D,(A\otimes B)\otimes C) \to \text{Hom}(D,A\otimes(B\otimes C)) + + by a matrix `F^{ABC}_D`. This depends on a pair of + additional simple objects `X` and `Y`. Indeed, we can + get a basis for `\text{Hom}(D,(A\otimes B)\otimes C)` + indexed by simple objects `X` in which the corresponding + homomorphism factors through `X\otimes C`, and similarly + `\text{Hom}(D,A\otimes(B\otimes C))` has a basis indexed + by `Y`, in which the basis vector factors through `A\otimes Y`. + + See [TTWL2009]_ for an introduction to this topic, + [EGNO2015]_ Section 4.9 for a precise mathematical + definition, and [Bond2007]_ Section 2.5 for a discussion + of how to compute the F-matrix. In addition to + [Bond2007]_, worked out F-matrices may be found in + [RoStWa2009]_ and [CHW2015]_. + + The F-matrix is only determined up to a *gauge*. This + is a family of embeddings `C\to A\otimes B` for + simple objects `A,B,C` such that `\text{Hom}(C,A\otimes B)` + is nonzero. Changing the gauge changes the F-matrix though + not in a very essential way. By varying the gauge it is + possible to make the F-matrices unitary, or it is possible + to make them cyclotomic. + + Due to the large number of equations we may fail to find a + Groebner basis if there are too many variables. + + + EXAMPLES:: + + sage: I = FusionRing("E8",2,conjugate=True) + sage: I.fusion_labels(["i0","p","s"],inject_variables=True) + sage: f = FMatrix(I,inject_variables=True); f + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients + + We've exported two sets of variables to the global namespace. + We created three variables ``i0, p, s`` to represent the + primary fields (simple elements) of the FusionRing. Creating + the FMatrix factory also created variables ``fx1,fx2, ... , fx14`` + in order to solve the hexagon and pentagon equations describing + the F-matrix. Since we called :class:`FMatrix` with the parameter + ``inject_variables=True``, these have been exported into the global + namespace. This is not necessary for the code to work but if you want + to run the code experimentally you may want access to these + variables. + + EXAMPLES:: + + sage: f.fmatrix(s,s,s,s) + [fx10 fx11] + [fx12 fx13] + + The F-matrix has not been computed at this stage, so + the F-matrix `F^{sss}_s` is filled with variables + ``fx10``, ``fx11``, ``fx12``, ``fx13``. The task is + to solve for these. + + As explained above The F-matrix `(F^{ABC}_D)_{X,Y}` + two other variables `X` and `Y`. We have methods to + tell us (depending on `A,B,C,D`) what the possibilities + for these are. In this example with `A=B=C=D=s` + both `X` and `Y` are allowed to be `i_0` or `s`. + + EXAMPLES:: + + sage: f.f_from(s,s,s,s), f.f_to(s,s,s,s) + ([i0, p], [i0, p]) + + The last two statments show that the possible values of + `X` and `Y` when `A=B=C=D=s` are `i_0` and `p`. + + The F-matrix is computed by solving the so-called + pentagon and hexagon equations. The *pentagon + equations* reflect the Mac Lane pentagon axiom in the + definition of a monoidal category. The hexagon relations + reflect the axioms of a *braided monoidal category*, + which are constraints on both the F-matrix and on + the R-matrix. Optionally, orthogonality constraints + may be imposed to obtain an orthogonal F-matrix. + + EXAMPLES:: + + sage: f.get_defining_equations("pentagons")[1:3] + [fx9*fx12 - fx2*fx13, fx3*fx8 - fx4*fx9] + sage: f.get_defining_equations("hexagons")[1:3] + [fx11*fx12 + (-zeta128^32)*fx13^2 + (-zeta128^48)*fx13, + fx10*fx11 + (-zeta128^32)*fx11*fx13 + (-zeta128^16)*fx11] + sage: f.get_orthogonality_constraints()[1:3] + [fx1^2 - 1, fx2^2 - 1] + + There are two methods available to compute an F-matrix. + The first, :meth:`find_cyclotomic_solution` uses only + the pentagon and hexagon relations. The second, + :meth:`find_orthogonal_solution` uses additionally + the orthogonality relations. There are some differences + that should be kept in mind. + + :meth:`find_cyclotomic_solution` currently works only with + smaller examples. For example the :class:`FusionRing` for `G_2` at + level 2 is too large. When it is available, this method + produces an F-matrix whose entries are in the same + cyclotomic field as the underlying :class:`FusionRing`. + + EXAMPLES:: + + sage: f.find_cyclotomic_solution() + Setting up hexagons and pentagons... + Finding a Groebner basis... + Solving... + Fixing the gauge... + adding equation... fx1 - 1 + adding equation... fx11 - 1 + Done! + + We now have access to the values of the F-matrix using + the methods :meth:`fmatrix` and :meth:`fmat`. + + EXAMPLES:: + + sage: f.fmatrix(s,s,s,s) + [(-1/2*zeta128^48 + 1/2*zeta128^16) 1] + [ 1/2 (1/2*zeta128^48 - 1/2*zeta128^16)] + sage: f.fmat(s,s,s,s,p,p) + (1/2*zeta128^48 - 1/2*zeta128^16) + + :meth:`find_orthogonal_solution` is much more powerful + and is capable of handling large cases, sometimes + quickly but sometimes (in larger cases) after hours of + computation. Its F-matrices are not always in the + cyclotomic field that is the base ring of the underlying + :class:`FusionRing`, but sometimes in an extension field adjoining + some square roots. When this happens, the :class:`FusionRing` is + modified, adding an attribute :attr:`_basecoer` that is + a coercion from the cyclotomic field to the field + containing the F-matrix. The field containing the F-matrix + is available through :meth:`field`. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B3",2)) + sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # long time (~100 s) + sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # long time + True + + sage: f = FMatrix(FusionRing("G2",2)) + sage: f.find_orthogonal_solution(verbose=False) # long time (~18 s) + sage: f.field() # long time + Algebraic Field + """ self._FR = fusion_ring if inject_variables and (self._FR._fusion_labels is None): self._FR.fusion_labels(fusion_label, inject_variables=True) @@ -268,7 +285,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab print ("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) self._poly_ring.inject_variables(get_main_globals()) self._var_to_sextuple, self._fvars = self.findcases(output=True) - self._var_to_idx = { var : idx for idx, var in enumerate(self._poly_ring.gens()) } + self._var_to_idx = {var : idx for idx, var in enumerate(self._poly_ring.gens())} + self._idx_to_sextuple = {i : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(self._poly_ring.ngens())} self._singles = self.singletons() #Base field attributes @@ -278,6 +296,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._non_cyc_roots = list() #Useful solver state attributes + self.ideal_basis = list() self._solved = set() self._ks = dict() self._nnz = self._get_known_nonz() @@ -299,29 +318,78 @@ def __repr__(self): """ return "F-Matrix factory for %s"%self._FR - def remaining_vars(self): - """ - Return a list of unknown F-symbols (reflects current stage of computation) - """ - return [var for var in self._poly_ring.gens() if var not in self._solved] - def clear_equations(self): """ - Clear the set of equations to be solved. + Clear the list of equations to be solved. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("E6",1)) + sage: f.get_defining_equations('hexagons', output=False) + sage: len(f.ideal_basis) + 6 + sage: f.clear_equations() + sage: len(f.ideal_basis) == 0 + True """ self.ideal_basis = list() def clear_vars(self): """ - Clear the set of variables. Also reset the set of solved F-symbols. + Reset the F-symbols. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("C4", 1)) + sage: fvars = f.get_fvars() + sage: some_key = sorted(fvars)[0] + sage: fvars[some_key] + fx0 + sage: fvars[some_key] = 1 + sage: f.get_fvars()[some_key] + 1 + sage: f.clear_vars() + sage: f.get_fvars()[some_key] + fx0 """ - self._fvars = { self._var_to_sextuple[key] : key for key in self._var_to_sextuple } + self._fvars = {self._var_to_sextuple[key] : key for key in self._var_to_sextuple} self._solved = set() def _reset_solver_state(self): """ Reset solver state and clear relevant cache. Used to ensure state variables are the same for each orthogonal solver run. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("F4",1)) + sage: K = f.field() + sage: len(f._nnz.nonzero_positions()) + 1 + sage: f.find_orthogonal_solution(verbose=False) + sage: K == f.field() + False + sage: f._reset_solver_state() + sage: K == f.field() + True + sage: f.FR()._basecoer is None + True + sage: f._poly_ring.base_ring() == K + True + sage: len(f._solved) == 0 + True + sage: len(f.ideal_basis) == 0 + True + sage: len(f._ks) == 0 + True + sage: len(f._nnz.nonzero_positions()) == 1 + True + sage: all(len(x.q_dimension.cache) == 0 for x in f.FR().basis()) + True + sage: len(f.FR().r_matrix.cache) == 0 + True + sage: len(f.FR().s_ij.cache) == 0 + True """ self._FR._basecoer = None self._field = self._FR.field() @@ -339,14 +407,25 @@ def _reset_solver_state(self): def _update_poly_ring_base_field(self,field): """ - Change base field of PolynomialRing and the corresponding + Change base field of ``PolynomialRing`` and the corresponding index attributes + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D4",1)) + sage: f._update_poly_ring_base_field(QQ) + sage: f._poly_ring.base_ring() == QQ + True + sage: all(fx in f._poly_ring for fx in f._var_to_idx) + True + sage: all(fx in f._poly_ring for fx in f._var_to_sextuple) + True """ new_poly_ring = self._poly_ring.change_ring(field) nvars = self._poly_ring.ngens() #Do some appropriate conversions - self._var_to_idx = { new_poly_ring.gen(i) : i for i in range(nvars) } - self._var_to_sextuple = { new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars) } + self._var_to_idx = {new_poly_ring.gen(i) : i for i in range(nvars)} + self._var_to_sextuple = {new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars)} self._poly_ring = new_poly_ring def fmat(self, a, b, c, d, x, y, data=True): @@ -415,7 +494,7 @@ def fmatrix(self,a,b,c,d): EXAMPLES:: - sage: f=FMatrix(FusionRing("A1",2,fusion_labels="c",inject_variables=True)) + sage: f = FMatrix(FusionRing("A1",2,fusion_labels="c",inject_variables=True)) sage: f.fmatrix(c1,c1,c1,c1) [fx0 fx1] [fx2 fx3] @@ -437,9 +516,29 @@ def fmatrix(self,a,b,c,d): def field(self): r""" - Return the base field containing the F-symbols. When ``self`` is initialized, - the field is set to be the cyclotomic field of the :class:`FusionRing` associated - to ``self``. The field may change after running :meth:`find_orthogonal_solution`. + Return the base field containing the F-symbols. + + When ``self`` is initialized, the field is set to be the + cyclotomic field of the :class:`FusionRing` associated + to ``self``. + + The field may change after running :meth:`find_orthogonal_solution`. + At that point, this method could return the + associated :class:`FusionRing`'s cyclotomic field, an + appropriate :class:`NumberField` that was computed on the fly + by the F-matrix solver, or the :class:`AlgebraicField` ``QQbar``. + + Depending on the ``CartanType`` of ``self``, the solver may need + to compute an extension field containing certain square roots that + do not belong to the associated :class:`FusionRing`'s cyclotomic field. + + In certain cases we revert to ``QQbar`` because the extension field + computation does not seem to terminate. See + :meth:`attempt_number_field_computation` for more details. + + The method :meth:`get_non_cyclotomic_roots` returns a list of + roots defining the extension of the :class:`FusionRing`'s + cyclotomic field needed to contain all F-symbols. EXAMPLES:: @@ -449,6 +548,16 @@ def field(self): sage: f.find_orthogonal_solution(verbose=False) sage: f.field() Number Field in a with defining polynomial y^64 - 16*y^62 + 104*y^60 - 320*y^58 + 258*y^56 + 1048*y^54 - 2864*y^52 - 3400*y^50 + 47907*y^48 - 157616*y^46 + 301620*y^44 - 322648*y^42 + 2666560*y^40 + 498040*y^38 + 54355076*y^36 - 91585712*y^34 + 592062753*y^32 - 1153363592*y^30 + 3018582788*y^28 - 4848467552*y^26 + 7401027796*y^24 - 8333924904*y^22 + 8436104244*y^20 - 7023494736*y^18 + 4920630467*y^16 - 2712058560*y^14 + 1352566244*y^12 - 483424648*y^10 + 101995598*y^8 - 12532920*y^6 + 1061168*y^4 - 57864*y^2 + 1681 + sage: phi = f.get_qqbar_embedding() + sage: [phi(r).n() for r in f.get_non_cyclotomic_roots()] + [-0.786151377757423 + 1.73579267033929e-59*I] + + .. NOTE:: + + Consider using ``self.field().optimized_representation()`` to + obtain an equivalent :class:`NumberField` with a defining + polynomial with smaller coefficients, for a more efficient + element representation. """ return self._field @@ -466,13 +575,15 @@ def FR(self): def findcases(self,output=False): """ - Return unknown F-matrix entries. If run with output=True, + Return unknown F-matrix entries. + + If run with ``output=True``, this returns two dictionaries; otherwise it just returns the number of unknown values. EXAMPLES:: - sage: f=FMatrix(FusionRing("G2",1,fusion_labels=("i0","t"))) + sage: f = FMatrix(FusionRing("G2",1,fusion_labels=("i0","t"))) sage: f.findcases() 5 sage: f.findcases(output=True) @@ -514,9 +625,9 @@ def singletons(self): EXAMPLES:: - sage: fm = FMatrix(FusionRing("E7", 1)) - sage: singles = fm.singletons() - sage: all(fm.fmatrix(*fm._var_to_sextuple[v][:4]).nrows() == 1 for v in singles) + sage: f = FMatrix(FusionRing("E7",1)) + sage: singles = f.singletons() + sage: all(f.fmatrix(*f._var_to_sextuple[v][:4]).nrows() == 1 for v in singles) True """ ret = [] @@ -586,8 +697,9 @@ def get_fvars(self): r""" Return a dictionary of F-symbols. - The keys are sextuples `(a,b,c,d,x,y)` basis elements of ``self`` and - the values are the corresponding F-symbols `(F^{a,b,c}_d)_{xy}`. + The keys are sextuples `(a,b,c,d,x,y)` of basis elements of + ``self.FR()`` and the values are the corresponding F-symbols + `(F^{a,b,c}_d)_{xy}`. These values reflect the current state of a solver's computation. @@ -604,6 +716,18 @@ def get_fvars(self): """ return self._fvars + def get_poly_ring(self): + r""" + Return the polynomial ring whose generators denote F-symbols we seek. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B6",1)) + sage: f.get_poly_ring() + Multivariate Polynomial Ring in fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 over Cyclotomic Field of order 96 and degree 32 + """ + return self._poly_ring + def get_non_cyclotomic_roots(self): r""" Return a list of roots that define the extension of the associated @@ -612,24 +736,30 @@ def get_non_cyclotomic_roots(self): OUTPUT: - The list of non-cyclotomic roots is given as a list of elements of - ``self.field()``. + The list of non-cyclotomic roots is given as a list of elements of the + field returned by :meth:`field()`. - If ``self.field() == self.FR().field()`` this method returns an empty list. - - When ``self.field()`` is a ``NumberField``, one may use - :meth:`get_qqbar_embedding` to embed the resulting values into ``QQbar``. + If ``self.field() == self.FR().field()`` then this method + returns an empty list. EXAMPLES:: sage: f = FMatrix(FusionRing("E6",1)) sage: f.find_orthogonal_solution(verbose=False) + sage: f.field() == f.FR().field() + True sage: f.get_non_cyclotomic_roots() [] - sage: f = FMatrix(FusionRing("E7",2)) # long time + sage: f = FMatrix(FusionRing("E7",2)) # long time sage: f.find_orthogonal_solution(verbose=False) # long time - sage: f.get_non_cyclotomic_roots() # long time + sage: f.field() == f.FR().field() # long time + False + sage: f.get_non_cyclotomic_roots() # long time [-0.7861513777574233?, -0.5558929702514212?] + + When ``self.field()`` is a ``NumberField``, one may use + :meth:`get_qqbar_embedding` to embed the resulting values into + ``QQbar``. """ return sorted(set(self._non_cyc_roots)) @@ -642,17 +772,30 @@ def get_qqbar_embedding(self): This embedding is useful for getting a better sense for the F-symbols, particularly when they are computed as elements of a - :class:`NumberField`. + :class:`NumberField`. See also :meth:`get_non_cyclotomic_roots`. EXAMPLES:: - sage: fm = FMatrix(FusionRing("C3",1), fusion_label="c", inject_variables=True) - creating variables fx1..fx71 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26, fx27, fx28, fx29, fx30, fx31, fx32, fx33, fx34, fx35, fx36, fx37, fx38, fx39, fx40, fx41, fx42, fx43, fx44, fx45, fx46, fx47, fx48, fx49, fx50, fx51, fx52, fx53, fx54, fx55, fx56, fx57, fx58, fx59, fx60, fx61, fx62, fx63, fx64, fx65, fx66, fx67, fx68, fx69, fx70 - sage: fm.find_orthogonal_solution(verbose=False) - sage: phi = fm.get_qqbar_embedding() - sage: phi(fm.get_fvars()[c1,c2,c2,c1,c1,c2]) - -0.61803399? + 0.?e-8*I + sage: f = FMatrix(FusionRing("F4",1), fusion_label="f", inject_variables=True) + creating variables fx1..fx5 + Defining fx0, fx1, fx2, fx3, fx4 + sage: f.find_orthogonal_solution() + Computing F-symbols for The Fusion Ring of Type F4 and level 1 with Integer Ring coefficients with 5 variables... + Set up 10 hex and orthogonality constraints... + Partitioned 10 equations into 2 components of size: + [4, 1] + Elimination epoch completed... 0 eqns remain in ideal basis + Hex elim step solved for 4 / 5 variables + Set up 0 reduced pentagons... + Pent elim step solved for 4 / 5 variables + Partitioned 0 equations into 0 components of size: + [] + Partitioned 1 equations into 1 components of size: + [1] + Computing appropriate NumberField... + sage: phi = f.get_qqbar_embedding() + sage: phi(f.fmat(f1,f1,f1,f1,f1,f1)).n() + -0.618033988749895 + 3.63089268571980e-21*I """ return self._qqbar_embedding @@ -662,6 +805,39 @@ def get_coerce_map_from_fr_cyclotomic_field(self): cyclotomic field into the base field containing all F-symbols (this could be the :class:`FusionRing`'s :class:`CyclotomicField`, a :class:`NumberField`, or ``QQbar``). + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("G2",1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: f.FR().field() + Cyclotomic Field of order 60 and degree 16 + sage: f.field() + Number Field in a with defining polynomial y^32 - 6*y^30 - 7*y^28 + 62*y^26 - 52*y^24 - 308*y^22 + 831*y^20 + 7496*y^18 + 18003*y^16 - 2252*y^14 + 42259*y^12 - 65036*y^10 + 29368*y^8 - 3894*y^6 + 377*y^4 - 22*y^2 + 1 + sage: phi = f.get_coerce_map_from_fr_cyclotomic_field() + sage: phi.domain() == f.FR().field() + True + sage: phi.codomain() == f.field() + True + + When F-symbols are computed as elements of the associated + :class:`FusionRing`'s base :class:`CyclotomicField`, + we have ``self.field() == self.FR().field()`` and this method + returns the identity map on ``self.field()``. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: phi = f.get_coerce_map_from_fr_cyclotomic_field() + sage: f.field() + Cyclotomic Field of order 48 and degree 16 + sage: f.field() == f.FR().field() + True + sage: phi.domain() == f.field() + True + sage: phi.is_identity() + True """ #If base field is different from associated FusionRing's CyclotomicField, #return coercion map @@ -672,14 +848,29 @@ def get_coerce_map_from_fr_cyclotomic_field(self): F = self._FR.field() return F.hom([F.gen()], F) - def get_fmats_in_alg_field(self): + def get_fvars_in_alg_field(self): r""" Return F-symbols as elements of the :class:`AlgebraicField` ``QQbar``. This method uses the embedding defined by - :meth:`self.get_qqbar_embedding` to coerce + :meth:`get_qqbar_embedding` to coerce F-symbols into ``QQbar``. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("G2",1), fusion_label="g", inject_variables=True) + creating variables fx1..fx5 + Defining fx0, fx1, fx2, fx3, fx4 + sage: f.find_orthogonal_solution(verbose=False) + sage: f.field() + Number Field in a with defining polynomial y^32 - 6*y^30 - 7*y^28 + 62*y^26 - 52*y^24 - 308*y^22 + 831*y^20 + 7496*y^18 + 18003*y^16 - 2252*y^14 + 42259*y^12 - 65036*y^10 + 29368*y^8 - 3894*y^6 + 377*y^4 - 22*y^2 + 1 + sage: f.get_fvars_in_alg_field() + {(g1, g1, g1, g0, g1, g1): 1, + (g1, g1, g1, g1, g0, g0): 0.61803399? + 0.?e-8*I, + (g1, g1, g1, g1, g0, g1): -0.7861514? + 0.?e-8*I, + (g1, g1, g1, g1, g1, g0): -0.7861514? + 0.?e-8*I, + (g1, g1, g1, g1, g1, g1): -0.61803399? + 0.?e-8*I} """ - return { sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items() } + return {sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items()} def get_radical_expression(self): """ @@ -687,15 +878,14 @@ def get_radical_expression(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",3), fusion_label="a", inject_variables=True) - creating variables fx1..fx71 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26, fx27, fx28, fx29, fx30, fx31, fx32, fx33, fx34, fx35, fx36, fx37, fx38, fx39, fx40, fx41, fx42, fx43, fx44, fx45, fx46, fx47, fx48, fx49, fx50, fx51, fx52, fx53, fx54, fx55, fx56, fx57, fx58, fx59, fx60, fx61, fx62, fx63, fx64, fx65, fx66, fx67, fx68, fx69, fx70 + sage: f = FMatrix(FusionRing("G2",1)) + sage: f.FR().fusion_labels("g", inject_variables=True) sage: f.find_orthogonal_solution(verbose=False) - sage: fvars = f.get_radical_expression() - sage: fvars[a1, a1, a1, a1, a2, a0] + sage: radical_fvars = f.get_radical_expression() + sage: radical_fvars[g1, g1, g1, g1, g1, g0] -sqrt(1/2*sqrt(5) - 1/2) """ - return { sextuple : val.radical_expression() for sextuple, val in self.get_fmats_in_alg_field().items() } + return {sextuple : val.radical_expression() for sextuple, val in self.get_fvars_in_alg_field().items()} ####################### ### Private helpers ### @@ -705,13 +895,46 @@ def _get_known_vals(self): r""" Construct a dictionary of ``idx``, ``known_val`` pairs used for substituting into remaining equations. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D4",1)) + sage: len(f._get_known_vals()) == 0 + True + sage: f.find_orthogonal_solution(verbose=False) + sage: len(f._get_known_vals()) == f._poly_ring.ngens() + True """ - return { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in self._solved } + return {var_idx : self._fvars[self._idx_to_sextuple[var_idx]] for var_idx in self._solved} def _get_known_sq(self,eqns=None): r""" Update ```self``'s dictionary of known squares. Keys are variable indices and corresponding values are the squares. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B5",1)) + sage: len(f._ks) == 0 + True + sage: f.get_orthogonality_constraints() + [fx0^2 - 1, + fx1^2 - 1, + fx2^2 - 1, + fx3^2 - 1, + fx4^2 - 1, + fx5^2 - 1, + fx6^2 - 1, + fx7^2 - 1, + fx8^2 - 1, + fx9^2 - 1, + fx10^2 + fx12^2 - 1, + fx10*fx11 + fx12*fx13, + fx10*fx11 + fx12*fx13, + fx11^2 + fx13^2 - 1] + sage: f.get_orthogonality_constraints(output=False) + sage: len(f._get_known_sq()) == 10 + True """ if eqns is None: eqns = self.ideal_basis @@ -726,30 +949,22 @@ def _get_known_nonz(self): r""" Construct an ETuple indicating positions of known nonzero variables. - NOTES: + .. NOTE:: MUST be called after ``self._ks = _get_known_sq()``. - """ - nonz = { self._var_to_idx[var] : 100 for var in self._singles } - for idx in self._ks: - nonz[idx] = 100 - return ETuple(nonz, self._poly_ring.ngens()) - ################################# - ### Useful private predicates ### - ################################# + This method is called by the constructor of ``self``. - def _is_univariate_in_unknown(self,monom_exp): - """ - Determine if monomial exponent is univariate in an unknown F-symbol - """ - return len(monom_exp.nonzero_values()) == 1 and monom_exp.nonzero_positions()[0] not in self._solved + EXAMPLES:: - def _is_uni_linear_in_unkwown(self,monom_exp): + sage: f = FMatrix(FusionRing("D5",1)) # indirect doctest + sage: f._nnz + (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) """ - Determine if monomial exponent is univariate and linear in an unknown F-symbol - """ - return monom_exp.nonzero_values() == [1] and monom_exp.nonzero_positions()[0] not in self._solved + nonz = {self._var_to_idx[var] : 100 for var in self._singles} + for idx in self._ks: + nonz[idx] = 100 + return ETuple(nonz, self._poly_ring.ngens()) ############################## ### Variables partitioning ### @@ -774,28 +989,32 @@ def get_fvars_by_size(self,n,indices=False): INPUT: - -``n`` -- positive integer - -``indices`` -- If ``indices`` is ``False`` (default), this method - returns a set of sextuples `(a,b,c,d,x,y)` identifying the - corresponding F-symbol. Each sextuple is a key in the dictionary - returned by :meth:`get_fvars`. + - `n` -- a positive integer + + - ``indices`` -- (default: ``False``) a boolean. + + If ``indices`` is ``False`` (default), + this method returns a set of sextuples `(a,b,c,d,x,y)` identifying + the corresponding F-symbol. Each sextuple is a key in the + dictionary returned by :meth:`get_fvars`. - Otherwise the method returns a list of integer indices that internally - identify the F-symbols. The ``indices=True`` option is meant - for internal use mostly. + Otherwise the method returns a list of integer indices that + internally identify the F-symbols. The ``indices=True`` option is + meant for internal use. EXAMPLES:: - sage: f = FMatrix(FusionRing("E8",2), inject_variables=True) - creating variables fx1..fx14 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + sage: f = FMatrix(FusionRing("A2",2), inject_variables=True) + creating variables fx1..fx287 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26, fx27, fx28, fx29, fx30, fx31, fx32, fx33, fx34, fx35, fx36, fx37, fx38, fx39, fx40, fx41, fx42, fx43, fx44, fx45, fx46, fx47, fx48, fx49, fx50, fx51, fx52, fx53, fx54, fx55, fx56, fx57, fx58, fx59, fx60, fx61, fx62, fx63, fx64, fx65, fx66, fx67, fx68, fx69, fx70, fx71, fx72, fx73, fx74, fx75, fx76, fx77, fx78, fx79, fx80, fx81, fx82, fx83, fx84, fx85, fx86, fx87, fx88, fx89, fx90, fx91, fx92, fx93, fx94, fx95, fx96, fx97, fx98, fx99, fx100, fx101, fx102, fx103, fx104, fx105, fx106, fx107, fx108, fx109, fx110, fx111, fx112, fx113, fx114, fx115, fx116, fx117, fx118, fx119, fx120, fx121, fx122, fx123, fx124, fx125, fx126, fx127, fx128, fx129, fx130, fx131, fx132, fx133, fx134, fx135, fx136, fx137, fx138, fx139, fx140, fx141, fx142, fx143, fx144, fx145, fx146, fx147, fx148, fx149, fx150, fx151, fx152, fx153, fx154, fx155, fx156, fx157, fx158, fx159, fx160, fx161, fx162, fx163, fx164, fx165, fx166, fx167, fx168, fx169, fx170, fx171, fx172, fx173, fx174, fx175, fx176, fx177, fx178, fx179, fx180, fx181, fx182, fx183, fx184, fx185, fx186, fx187, fx188, fx189, fx190, fx191, fx192, fx193, fx194, fx195, fx196, fx197, fx198, fx199, fx200, fx201, fx202, fx203, fx204, fx205, fx206, fx207, fx208, fx209, fx210, fx211, fx212, fx213, fx214, fx215, fx216, fx217, fx218, fx219, fx220, fx221, fx222, fx223, fx224, fx225, fx226, fx227, fx228, fx229, fx230, fx231, fx232, fx233, fx234, fx235, fx236, fx237, fx238, fx239, fx240, fx241, fx242, fx243, fx244, fx245, fx246, fx247, fx248, fx249, fx250, fx251, fx252, fx253, fx254, fx255, fx256, fx257, fx258, fx259, fx260, fx261, fx262, fx263, fx264, fx265, fx266, fx267, fx268, fx269, fx270, fx271, fx272, fx273, fx274, fx275, fx276, fx277, fx278, fx279, fx280, fx281, fx282, fx283, fx284, fx285, fx286 sage: f.largest_fmat_size() 2 sage: f.get_fvars_by_size(2) - {(f2, f2, f2, f2, f0, f0), - (f2, f2, f2, f2, f0, f1), - (f2, f2, f2, f2, f1, f0), - (f2, f2, f2, f2, f1, f1)} + {(f2, f2, f2, f4, f1, f1), + (f2, f2, f2, f4, f1, f5), + ... + (f4, f4, f4, f4, f4, f0), + (f4, f4, f4, f4, f4, f4)} """ fvars_copy = deepcopy(self._fvars) solved_copy = deepcopy(self._solved) @@ -809,8 +1028,8 @@ def get_fvars_by_size(self,n,indices=False): self._fvars = fvars_copy self._solved = solved_copy if indices: - return { self._var_to_idx[fx] for fx in var_set } - return { self._var_to_sextuple[fx] for fx in var_set } + return {self._var_to_idx[fx] for fx in var_set} + return {self._var_to_sextuple[fx] for fx in var_set} ############################ ### Checkpoint utilities ### @@ -818,31 +1037,93 @@ def get_fvars_by_size(self,n,indices=False): def save_fvars(self, filename): r""" - Save computed F-symbols for later use. This method should only be used - AFTER successfully one of the solvers, e.g. - :meth:`find_cyclotomic_solution` or :meth:`find_orthogonal_solution`. + Save computed F-symbols for later use. - Specify the ``filename`` as a string for storing computed F-symbols in a - pickle file. - """ + INPUT: + + - ``filename`` -- a string specifying the name of the pickle file + to be used. + + The current directory is used unless an absolute path to a file in + a different directory is provided. + + .. NOTE:: + + This method should only be used *after* successfully running one + of the solvers, e.g. :meth:`find_cyclotomic_solution` or + :meth:`find_orthogonal_solution`. + + When used in conjunction with :meth:`load_fvars`, this method may + be used to restore state of an :class:`FMatrix` object at the end + of a successful F-matrix solver run. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: fvars = f.get_fvars() + sage: K = f.field() + sage: filename = f.get_fr_str() + "_solver_results.pickle" + sage: f.save_fvars(filename) + sage: del f + sage: f2 = FMatrix(FusionRing("A2",1)) + sage: f2.load_fvars(filename) + sage: fvars == f2.get_fvars() + True + sage: K == f2.field() + True + sage: os.remove(filename) + """ + final_state = [ + self._fvars, + self._non_cyc_roots, + self.get_coerce_map_from_fr_cyclotomic_field(), + self._qqbar_embedding, + ] with open(filename, 'wb') as f: - pickle.dump(self._fvars, f) + pickle.dump(final_state, f) def load_fvars(self, filename): r""" - Load previously computed F-symbols from a pickle file generated by - :meth:`save_fvars`. This method does not work with intermediate - checkpoint pickles; it only works with pickles containing ALL F-symbols. + Load previously computed F-symbols from a pickle file. + + See :meth:`save_fvars` for more information. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: fvars = f.get_fvars() + sage: K = f.field() + sage: filename = f.get_fr_str() + "_solver_results.pickle" + sage: f.save_fvars(filename) + sage: del f + sage: f2 = FMatrix(FusionRing("A2",1)) + sage: f2.load_fvars(filename) + sage: fvars == f2.get_fvars() + True + sage: K == f2.field() + True + sage: os.remove(filename) + + .. NOTE:: + + :meth:`save_fvars`. This method does not work with intermediate + checkpoint pickles; it only works with pickles containing *all* + F-symbols, i.e. those created by :meth:`save_fvars` and by + specifying an optional ``save_results`` parameter for + :meth:`find_orthogonal_solution`. """ with open(filename, 'rb') as f: - self._fvars = pickle.load(f) - #Update class state attributes - self.symbols_known = True + self._fvars, self._non_cyc_roots, self._coerce_map_from_cyc_field, self._qqbar_embedding = pickle.load(f) + #Update state attributes + self._chkpt_status = 7 self._solved = set(range(self._poly_ring.ngens())) + self._field = self._qqbar_embedding.domain() def get_fr_str(self): """ - Auto-generate identifying key for saving results + Auto-generate an identifying key for saving results EXAMPLES:: @@ -855,14 +1136,74 @@ def get_fr_str(self): def _checkpoint(self,do_chkpt,status,verbose=True): """ - Pickle current solver state + Pickle current solver state. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: f._reset_solver_state() + sage: f.get_orthogonality_constraints(output=False) + sage: f.get_defining_equations('hexagons',output=False) + sage: f.ideal_basis = f._par_graph_gb(verbose=False) + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._triangular_elim(verbose=False) + sage: f._update_reduction_params(children_need_update=False) + sage: f._checkpoint(do_chkpt=True,status=2) + Checkpoint 2 reached! + sage: del f + sage: f = FMatrix(FusionRing("A1",3)) + sage: f.find_orthogonal_solution(warm_start="fmatrix_solver_checkpoint_A13.pickle") + Computing F-symbols for The Fusion Ring of Type A1 and level 3 with Integer Ring coefficients with 71 variables... + Set up 121 reduced pentagons... + Elimination epoch completed... 18 eqns remain in ideal basis + Elimination epoch completed... 5 eqns remain in ideal basis + Pent elim step solved for 64 / 71 variables + Partitioned 5 equations into 1 components of size: + [4] + Elimination epoch completed... 0 eqns remain in ideal basis + Partitioned 6 equations into 6 components of size: + [1, 1, 1, 1, 1, 1] + Computing appropriate NumberField... + sage: f._chkpt_status == 7 + True + sage: len(f._solved) == f._poly_ring.ngens() + True + sage: os.remove("fmatrix_solver_checkpoint_A13.pickle") + sage: f = FMatrix(FusionRing("A1",2)) + sage: f._reset_solver_state() + sage: f.get_orthogonality_constraints(output=False) + sage: f.get_defining_equations('hexagons',output=False) + sage: f.ideal_basis = f._par_graph_gb(verbose=False) + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._triangular_elim(verbose=False) + sage: f._update_reduction_params(children_need_update=False) + sage: f.get_defining_equations('pentagons',output=False) + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._triangular_elim(verbose=False) + sage: f._checkpoint(do_chkpt=True,status=4) + Checkpoint 4 reached! + sage: del f + sage: f = FMatrix(FusionRing("A1",2)) + sage: f.find_orthogonal_solution(warm_start="fmatrix_solver_checkpoint_A12.pickle") + Computing F-symbols for The Fusion Ring of Type A1 and level 2 with Integer Ring coefficients with 14 variables... + Partitioned 0 equations into 0 components of size: + [] + Partitioned 2 equations into 2 components of size: + [1, 1] + sage: f._chkpt_status == 7 + True + sage: len(f._solved) == f._poly_ring.ngens() + True + sage: os.remove("fmatrix_solver_checkpoint_A12.pickle") """ if not do_chkpt: return filename = "fmatrix_solver_checkpoint_" + self.get_fr_str() + ".pickle" - eqns = self.ideal_basis + self._add_square_fixers(output=True) with open(filename, 'wb') as f: - pickle.dump([self._fvars, self._solved, eqns, status], f) + # pickle.dump([self._fvars, self._solved, eqns, status], f) + pickle.dump([self._fvars, self._solved, self._ks, self.ideal_basis, status], f) if verbose: print(f"Checkpoint {status} reached!") @@ -870,15 +1211,59 @@ def _restore_state(self,filename): r""" Load solver state from file. Use this method both for warm-starting :meth:`find_orthogonal_solution` and to load pickled results. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: f._reset_solver_state() + sage: f.get_orthogonality_constraints(output=False) + sage: f.get_defining_equations('hexagons',output=False) + sage: f.ideal_basis = f._par_graph_gb(verbose=False) + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._triangular_elim(verbose=False) + sage: f._update_reduction_params(children_need_update=False) + sage: fvars = f._fvars + sage: ib = f.ideal_basis + sage: solved = f._solved + sage: ks = f._ks + sage: status = f._chkpt_status + sage: f._checkpoint(do_chkpt=True,status=2) + Checkpoint 2 reached! + sage: del f + sage: f = FMatrix(FusionRing("A1",3)) + sage: f._restore_state("fmatrix_solver_checkpoint_A13.pickle") + sage: fvars == f._fvars + True + sage: ib == f.ideal_basis + True + sage: ks == f._ks + True + sage: solved == f._solved + True + sage: 2 == f._chkpt_status + True + sage: os.remove("fmatrix_solver_checkpoint_A13.pickle") + + TESTS:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: f.find_orthogonal_solution(save_results="test.pickle",verbose=False) # long time + sage: del f + sage: f = FMatrix(FusionRing("A1",3)) + sage: f.find_orthogonal_solution(warm_start="test.pickle") # long time + sage: f._chkpt_status == 7 # long time + True + sage: os.remove("test.pickle") # long time """ with open(filename, 'rb') as f: state = pickle.load(f) #Loading saved results pickle - if type(state) == type(dict()): + if len(state) == 4: self.load_fvars(filename) self._chkpt_status = 7 return - self._fvars, self._solved, self.ideal_basis, self._chkpt_status = state + self._fvars, self._solved, self._ks, self.ideal_basis, self._chkpt_status = state self._update_reduction_params() ################# @@ -888,15 +1273,14 @@ def _restore_state(self,filename): def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): r""" Apply the given mapper to each element of the given input iterable and - return the results (with no duplicates) in a list. This method applies - the mapper in parallel if a worker_pool is provided. + return the results (with no duplicates) in a list. INPUT: -``mapper`` -- string specifying the name of a function defined in the ``fast_parallel_fmats_methods`` module. - NOTES: + .. NOTE:: If ``worker_pool`` is not provided, function maps and reduces on a single process. @@ -904,7 +1288,18 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t whether it should use multiprocessing based on the length of the input iterable. If it can't determine the length of the input iterable then it uses multiprocessing with the default chunksize of - `1` if chunksize is not explicitly provided. + `1` unless a chunksize is provided. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",2)) + sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1)])) + 11 + sage: from multiprocessing import Pool + sage: pool = Pool() + sage: mp_params = [(i,pool._processes) for i in range(pool._processes)] + sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=pool,chunksize=1,mp_thresh=0)) + 33 """ if mp_thresh is None: mp_thresh = self.mp_thresh @@ -944,14 +1339,36 @@ def get_orthogonality_constraints(self,output=True): INPUT: - -``output``-- a boolean. + - ``output`` -- a boolean. + + OUTPUT: - If ``output==True``, orthogonality constraints are returned as + If ``output=True``, orthogonality constraints are returned as polynomial objects. Otherwise, the constraints are appended to ``self.ideal_basis``. - They are stored in the internal tuple representation. The ``output=False`` - option is meant mostly for internal use by the F-matrix solver. + They are stored in the internal tuple representation. The + ``output=False`` option is meant mostly for internal use by the + F-matrix solver. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B4",1)) + sage: f.get_orthogonality_constraints() + [fx0^2 - 1, + fx1^2 - 1, + fx2^2 - 1, + fx3^2 - 1, + fx4^2 - 1, + fx5^2 - 1, + fx6^2 - 1, + fx7^2 - 1, + fx8^2 - 1, + fx9^2 - 1, + fx10^2 + fx12^2 - 1, + fx10*fx11 + fx12*fx13, + fx10*fx11 + fx12*fx13, + fx11^2 + fx13^2 - 1] """ eqns = list() for tup in product(self._FR.basis(), repeat=4): @@ -966,21 +1383,50 @@ def get_defining_equations(self,option,worker_pool=None,output=True): Get the equations defining the ideal generated by the hexagon or pentagon relations. - Use ``option='hexagons'`` to get equations imposed on the F-matrix by the hexagon - relations in the definition of a braided category. + INPUT: + + - ``option`` -- a string determining equations to be set up. + + Use ``option='hexagons'`` to get equations imposed on the F-matrix by + the hexagon relations in the definition of a braided category. - Use ``option='pentagons'`` to get equations imposed on the F-matrix by the pentagon - relations in the definition of a monoidal category. + Use ``option='pentagons'`` to get equations imposed on the F-matrix by + the pentagon relations in the definition of a monoidal category. + + - ``worker_pool`` -- (default: ``None``) a ``Pool`` object of the + Python ``multiprocessing`` module. + + If a ``worker_pool`` object is passed, we distribute the work + amongst processes in the pool. + + - ``output`` -- (default: ``True``) a boolean indicating whether + results should be returned. If ``output=True``, equations are returned as polynomial objects. Otherwise, the constraints are appended to ``self.ideal_basis``. - They are stored in the internal tuple representation. The ``output==False`` - option is meant mostly for internal use by the F-matrix solver. + They are stored in the internal tuple representation. The + ``output=False`` option is meant mostly for internal use by the + F-matrix solver. + + EXAMPLES:: - If a ``worker_pool`` object is passed, then we use multiprocessing. - The ``worker_pool`` object is assumed to be a ``Pool`` object of the - Python ``multiprocessing`` module. + sage: f = FMatrix(FusionRing("B2",1)) + sage: f.get_defining_equations('hexagons') + [fx0 - 1, + fx10^2 + (-zeta32^8)*fx11*fx12 + (-zeta32^12)*fx10, + fx11*fx12 + (-zeta32^8)*fx13^2 + (zeta32^12)*fx13, + fx2 + 1, + fx7 + 1, + fx3*fx8 - fx6, + fx1*fx5 + fx2, + fx6 - 1, + fx4*fx9 + fx7, + fx10*fx11 + (-zeta32^8)*fx11*fx13 + (zeta32^4)*fx11, + fx10*fx12 + (-zeta32^8)*fx12*fx13 + (zeta32^4)*fx12] + sage: pe = f.get_defining_equations('pentagons') + sage: len(pe) + 33 """ n_proc = worker_pool._processes if worker_pool is not None else 1 params = [(child_id, n_proc) for child_id in range(n_proc)] @@ -996,74 +1442,59 @@ def get_defining_equations(self,option,worker_pool=None,output=True): ### Equations processing ### ############################ - def tup_to_fpoly(self,eq_tup): - """ - Assemble a polynomial object from its tuple representation - """ - return tup_to_poly(eq_tup,parent=self._poly_ring) - def _tup_to_fpoly(self,eq_tup): r""" - Faster version of :meth:`tup_to_fpoly`. Unsafe for client use, since it - avoids implicit casting and it may lead to segmentation faults. - """ - return _tup_to_poly(eq_tup,parent=self._poly_ring) + Assemble a polynomial object from its tuple representation. - def _solve_for_linear_terms(self,eqns=None): - """ - Solve for a linear term occurring in a two-term equation. + .. WARNING:: - Also solve for variables that are zero. - """ - if eqns is None: - eqns = self.ideal_basis + This method avoids implicit casting when constructing a + polynomial object, and may therefore lead to SEGFAULTs. + It is meant for internal use by the F-matrix solver. - F = self._field - linear_terms_exist = False - for eq_tup in eqns: + This method is a left inverse of + :meth:`sage.combinat.root_system.poly_tup_engine.poly_to_tup`. - #Only unflatten relevant polynomials - if len(eq_tup) > 2: - continue - eq_tup = _unflatten_coeffs(F, eq_tup) - - if len(eq_tup) == 1: - m = eq_tup[0][0] - if self._is_univariate_in_unknown(m): - var = m.nonzero_positions()[0] - self._fvars[self._var_to_sextuple[self._poly_ring.gen(var)]] = tuple() - self._solved.add(var) - linear_terms_exist = True - if len(eq_tup) == 2: - monomials = [m for m, c in eq_tup] - max_var = monomials[0].emax(monomials[1]).nonzero_positions()[0] - for this, m in enumerate(monomials): - other = (this+1)%2 - if self._is_uni_linear_in_unkwown(m) and m.nonzero_positions()[0] == max_var and monomials[other][m.nonzero_positions()[0]] == 0: - var = m.nonzero_positions()[0] - rhs_key = monomials[other] - rhs_coeff = -eq_tup[other][1] / eq_tup[this][1] - self._fvars[self._var_to_sextuple[self._poly_ring.gen(var)]] = ((rhs_key,rhs_coeff),) - self._solved.add(var) - linear_terms_exist = True - return linear_terms_exist - - def _backward_subs(self): - """ - Backward substitution step. Traverse variables in reverse lexicographical order. - """ - one = self._field.one() - for var in reversed(self._poly_ring.gens()): - sextuple = self._var_to_sextuple[var] - rhs = self._fvars[sextuple] - d = { var_idx : self._fvars[self._var_to_sextuple[self._poly_ring.gen(var_idx)]] for var_idx in variables(rhs) if var_idx in self._solved } - if d: - kp = compute_known_powers(get_variables_degrees([rhs]), d, one) - self._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one),self._ks).items()) + EXAMPLES:: + + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: f = FMatrix(FusionRing("C3",1)) + sage: from multiprocessing import Pool, set_start_method + sage: try: + ....: set_start_method('fork') + ....: except: + ....: pass + sage: pool = Pool() + sage: he = f.get_defining_equations('hexagons',pool) + sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) + True + """ + return _tup_to_poly(eq_tup,parent=self._poly_ring) def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): """ - Update reduction parameters in all processes + Update reduction parameters that are solver state attributes. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: f.get_orthogonality_constraints(output=False) + sage: from multiprocessing import Pool, set_start_method + sage: try: + ....: set_start_method('fork') + ....: except: + ....: pass + sage: pool = Pool() + sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) + sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f.mp_thresh = 0 + sage: f._triangular_elim(worker_pool=pool) # indirect doctest + Elimination epoch completed... 10 eqns remain in ideal basis + Elimination epoch completed... 0 eqns remain in ideal basis + sage: f.ideal_basis + [] """ if eqns is None: eqns = self.ideal_basis @@ -1076,40 +1507,50 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat new_data = [(self._fvars,self._solved,self._ks,self._var_degs)]*n_proc self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) - def _triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose=True): + def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): """ - Perform triangular elimination of linear terms in two-term equations until no such terms exist - For optimal usage of TRIANGULAR elimination, pass in a SORTED list of equations + Perform triangular elimination of linear terms in two-term equations + until no such terms exist. + + .. NOTE:: + + For optimal usage of TRIANGULAR elimination, pass in a + SORTED list of equations. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D3",1)) + sage: f.get_defining_equations('hexagons',output=False) + sage: f.get_orthogonality_constraints(output=False) + sage: gb = f._par_graph_gb(verbose=False) + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis = sorted(gb, key=poly_tup_sortkey) + sage: f._triangular_elim() + Elimination epoch completed... 0 eqns remain in ideal basis + sage: f.ideal_basis + [] """ ret = True if eqns is None: eqns = self.ideal_basis ret = False - if required_vars is None: - required_vars = self._poly_ring.gens() - poly_sortkey = poly_tup_sortkey_degrevlex - #Unzip polynomials - self._fvars = { sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items() } + self._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items()} while True: - linear_terms_exist = self._solve_for_linear_terms(eqns) + linear_terms_exist = _solve_for_linear_terms(self,eqns) if not linear_terms_exist: break - self._backward_subs() - - #Support early termination in case only some F-symbols are needed - req_vars_known = all(self._fvars[self._var_to_sextuple[var]] in self._FR.field() for var in required_vars) - if req_vars_known: return 1 + _backward_subs(self) #Compute new reduction params, send to child processes if any, and update eqns self._update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool) - eqns.sort(key=poly_sortkey) + eqns.sort(key=poly_tup_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) #Zip up _fvars before exiting - self._fvars = { sextuple : self._tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items() } + self._fvars = {sextuple : self._tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items()} if ret: return eqns self.ideal_basis = eqns @@ -1120,34 +1561,111 @@ def _triangular_elim(self,required_vars=None,eqns=None,worker_pool=None,verbose= def equations_graph(self,eqns=None): """ - Construct a graph corresponding to equations. The nodes in the graph - are indices corresponding to variables in the equations and two - nodes are connected if the corresponding variables appear together in - a given equation. + Construct a graph corresponding to the given equations. + + Every node corresponds to a variable and nodes are connected when + the corresponding variables appear together in an equation. + + INPUT: - If no list of equations is passed, the graph is built from equations in - ``self.ideal_basis``. + - ``eqns`` -- a list of polynomials. + + Each polynomial is either an object in the ring returned by + :meth:`get_poly_ring` or it is a tuple of pairs representing + a polynomial using the internal representation. + + If no list of equations is passed, the graph is built from the + polynomials in ``self.ideal_basis``. In this case the method assumes + the internal representation of a polynomial as a tuple of pairs is + used. + + This method is crucial to :meth:`find_orthogonal_solution`. The + hexagon equations, obtained using :meth:`get_defining_equations`, + define a disconnected graph that breaks up into many small components. + The :meth:`find_orthogonal_solution` solver exploits this when + undertaking a Groebner basis computation. + + OUTPUT: + + A ``Graph`` object. If a list of polynomial objects was given, + the set of nodes in the output graph is the subset polynomial + ring generators appearing in the equations. + + If the internal representation was used, the set of nodes is + the subset of indices corresponding to polynomial ring generators. + This option is meant for internal use by the F-matrix solver. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A3",1)) + sage: f.get_poly_ring().ngens() + 27 + sage: he = f.get_defining_equations('hexagons') + sage: graph = f.equations_graph(he) + sage: graph.connected_components_sizes() + [6, 3, 3, 3, 3, 3, 3, 1, 1, 1] """ if eqns is None: eqns = self.ideal_basis - G = sage.graphs.generators.basic.EmptyGraph() - G.add_vertices([x for eq_tup in eqns for x in variables(eq_tup)]) - for eq_tup in eqns: - s = [v for v in variables(eq_tup)] + G = Graph() + if not eqns: return G + + #Eqns could be a list of poly objects or poly tuples stored in internal repn + if type(eqns[0]) == tuple: + G.add_vertices([x for eq_tup in eqns for x in variables(eq_tup)]) + else: + G.add_vertices([x for eq in eqns for x in eq.variables()]) + for eq in eqns: + #Eqns could be a list of poly objects or poly tuples stored in internal repn + if type(eq) == tuple: + s = [v for v in variables(eq)] + else: + s = [v for v in eq.variables()] for x in s: for y in s: if y!=x: G.add_edge(x,y) return G - def partition_eqns(self,graph,eqns=None,verbose=True): + def _partition_eqns(self,eqns=None,verbose=True): """ - Partition equations corresponding to edges in a disconnected graph + Partition equations corresponding to edges in a disconnected graph. + + OUTPUT: + + This method returns a dictionary of (c, e) pairs, where + c is a tuple denoting a connected component in the graph produced + by calling :meth:`equations_graph` with the given ``eqns`` and + e is a list of all equations with variables in c. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("C2",1)) + sage: f.get_defining_equations('hexagons',output=False) + sage: partition = f._partition_eqns() + Partitioned 11 equations into 5 components of size: + [4, 3, 3, 3, 1] + sage: from sage.combinat.root_system.poly_tup_engine import variables + sage: for c, e in partition.items(): + ....: assert set(v for eq_tup in e for v in variables(eq_tup)) == set(c) + sage: vars_in_partition = set() + sage: eqns_in_partition = set() + sage: for c, e in partition.items(): + ....: vars_in_partition.update(c) + ....: eqns_in_partition.update(e) + sage: vars_in_partition == set(v for eq_tup in f.ideal_basis for v in variables(eq_tup)) + True + sage: eqns_in_partition == set(f.ideal_basis) + True + sage: from itertools import product + sage: for e1, e2 in product(partition.values(),repeat=2): + ....: assert e1 == e2 or set(e1).isdisjoint(set(e2)) """ if eqns is None: eqns = self.ideal_basis - partition = { tuple(c) : [] for c in graph.connected_components() } + graph = self.equations_graph(eqns) + partition = {tuple(c) : [] for c in graph.connected_components()} for eq_tup in eqns: partition[tuple(graph.connected_component_containing_vertex(variables(eq_tup)[0]))].append(eq_tup) if verbose: @@ -1155,27 +1673,43 @@ def partition_eqns(self,graph,eqns=None,verbose=True): print(graph.connected_components_sizes()) return partition - def _add_square_fixers(self,output=False): - """ - Add square fixing equations back to ideal basis - """ - sq_fixers = list() - n = self._poly_ring.ngens() - one = self._field.one() - for fx, rhs in self._ks.items(): - if fx not in self._solved: - lt = (ETuple({ fx : 2 },n), one) - sq_fixers.append((lt, (ETuple({},n), -rhs))) - if output: - return sq_fixers - self.ideal_basis.extend(sq_fixers) - def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): """ - Compute a Groebner basis for a set of equations partitioned according to their corresponding graph + Compute a Groebner basis for a list of equations partitioned according + to their corresponding graph. + + .. NOTE:: + + If the graph has more than 50 components, this method computes the + Groebner basis in parallel when a ``worker_pool`` is provided. + + This method will refuse to find a Groebner basis for a component + of size larger than 60, since such a calculation does not seem to + terminate. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("F4",1)) + sage: f.get_orthogonality_constraints(output=False) + sage: from multiprocessing import Pool, set_start_method + sage: try: + ....: set_start_method('fork') # context can be set only once + ....: except RuntimeError: + ....: pass + sage: pool = Pool() + sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) + sage: gb = f._par_graph_gb(worker_pool=pool) + Partitioned 10 equations into 2 components of size: + [4, 1] + sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs + sage: [f._tup_to_fpoly(_unflatten_coeffs(f.field(), t)) for t in gb] + [fx0 - 1, + fx2 - fx3, + fx3^2 + (zeta80^24 - zeta80^16), + fx4 + (-zeta80^24 + zeta80^16), + fx1 + (zeta80^24 - zeta80^16)] """ if eqns is None: eqns = self.ideal_basis - graph = self.equations_graph(eqns) small_comps = list() temp_eqns = list() @@ -1185,7 +1719,7 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose # for i in range(nmax+1): # vars_by_size.append(self.get_fvars_by_size(i)) - for comp, comp_eqns in self.partition_eqns(graph,verbose=verbose).items(): + for comp, comp_eqns in self._partition_eqns(verbose=verbose).items():#self._partition_eqns(graph,verbose=verbose).items(): #Check if component is too large to process if len(comp) > 60: # fmat_size = 0 @@ -1206,19 +1740,44 @@ def _get_component_variety(self,var,eqns): """ Translate equations in each connected component to smaller polynomial rings so we can call built-in variety method. + + INPUT: + + - ``var`` -- a list of variable indices + - ``eqns`` -- a list of polynomial equations in the internal + tuple of pairs representation + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("G2",2)) + sage: from multiprocessing import Pool, set_start_method + sage: try: + ....: set_start_method('fork') # context can be set only once + ....: except RuntimeError: + ....: pass + sage: f.get_defining_equations('hexagons',worker_pool=Pool(),output=False) # long time + sage: partition = f._partition_eqns() # long time + Partitioned 327 equations into 35 components of size: + [27, 27, 27, 24, 24, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 9, 9, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1] + sage: c = (216, 292, 319) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: eqns = partition[c] + [poly_to_tup(f._poly_ring.gen(216)-1)] # long time + sage: f._get_component_variety(c,eqns) # long time + [{216: -1, 292: -1, 319: 1}] """ #Define smaller poly ring in component vars R = PolynomialRing(self._FR.field(),len(var),'a',order='lex') #Zip tuples into R and compute Groebner basis - idx_map = { old : new for new, old in enumerate(sorted(var)) } + idx_map = {old : new for new, old in enumerate(sorted(var))} nvars = len(var) - polys = [tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R) for eq_tup in eqns] + eqns = [_unflatten_coeffs(self._field,eq_tup) for eq_tup in eqns] + polys = [_tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R) for eq_tup in eqns] var_in_R = Ideal(sorted(polys)).variety(ring=AA) #Change back to fmats poly ring and append to temp_eqns - inv_idx_map = { v : k for k, v in idx_map.items() } - return [{ inv_idx_map[i] : value for i, (key, value) in enumerate(sorted(soln.items())) } for soln in var_in_R] + inv_idx_map = {v : k for k, v in idx_map.items()} + return [{inv_idx_map[i] : value for i, (key, value) in enumerate(sorted(soln.items()))} for soln in var_in_R] ####################### ### Solution method ### @@ -1226,23 +1785,36 @@ def _get_component_variety(self,var,eqns): def attempt_number_field_computation(self): """ - Based on the ``CartanType`` of ``self``, determine whether to attempt - to find a :class:`NumberField` containing all the F-symbols based on data - known on March 17, 2021. + Based on the ``CartanType`` of ``self`` and data + known on March 17, 2021, determine whether to attempt + to find a :class:`NumberField` containing all the F-symbols. + + This method is used by :meth:`find_orthogonal_solution` + to determine a field containing all F-symbols. + See :meth:`field` and :meth:`get_non_cyclotomic_roots`. - For certain :class:`FusionRing` 's, the number field computation does - not terminate in a reasonable amount of time. + For certain :class:`FusionRing`'s, the number field computation does + not terminate in reasonable time. In these cases, we report F-symbols as elements - of the ``AlgebraicField`` :class:`QQbar`. + of the :class:`AlgebraicField` ``QQbar``. EXAMPLES:: - sage: f = FMatrix(FusionRing("E6",2)) + sage: f = FMatrix(FusionRing("F4",2)) sage: f.attempt_number_field_computation() False sage: f = FMatrix(FusionRing("G2",1)) sage: f.attempt_number_field_computation() True + + .. NOTE:: + + In certain cases, F-symbols are found in the associated + :class:`FusionRing`'s cyclotomic field and a + :class:`NumberField` computation is not needed. In these + cases this method returns ``True`` but the + :meth:`find_orthogonal_solution` solver does *not* + undertake a :class:`NumberField` computation. """ ct = self._FR.cartan_type() k = self._FR._k @@ -1268,37 +1840,69 @@ def _get_explicit_solution(self,eqns=None,verbose=True): terms of Groeber basis. A few degrees of freedom remain. By specializing the free variables and back substituting, a solution in the base field is now obtained. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",2)) # indirect doctest + sage: f.find_orthogonal_solution() # long time + Computing F-symbols for The Fusion Ring of Type A2 and level 2 with Integer Ring coefficients with 287 variables... + Set up 548 hex and orthogonality constraints... + Partitioned 548 equations into 57 components of size: + [24, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1] + Elimination epoch completed... 53 eqns remain in ideal basis + Elimination epoch completed... 5 eqns remain in ideal basis + Elimination epoch completed... 0 eqns remain in ideal basis + Hex elim step solved for 203 / 287 variables + Set up 994 reduced pentagons... + Elimination epoch completed... 699 eqns remain in ideal basis + Elimination epoch completed... 279 eqns remain in ideal basis + Elimination epoch completed... 9 eqns remain in ideal basis + Pent elim step solved for 270 / 287 variables + Partitioned 9 equations into 1 components of size: + [5] + Elimination epoch completed... 0 eqns remain in ideal basis + Partitioned 16 equations into 16 components of size: + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + Computing appropriate NumberField... """ if eqns is None: eqns = self.ideal_basis #Don't add square fixers when warm starting from a late-stage checkpoint if self._chkpt_status < 5: - self._add_square_fixers() - eqns_partition = self.partition_eqns(self.equations_graph(eqns),verbose=verbose) + # self._add_square_fixers() + n = self._poly_ring.ngens() + one = self._field.one() + for fx, rhs in self._ks.items(): + if fx not in self._solved: + lt = (ETuple({fx : 2},n), one) + eqns.append((lt, (ETuple({},n), -rhs))) + eqns_partition = self._partition_eqns(verbose=verbose) F = self._field - x = F['x'].gen() + R = F['x'] numeric_fvars = dict() non_cyclotomic_roots = list() must_change_base_field = False phi = F.hom([F.gen()],F) for comp, part in eqns_partition.items(): - #If component have only one equation in a single variable, get a root + #If component has only one equation in a single variable, get a root if len(comp) == 1 and len(part) == 1: #Attempt to find cyclotomic root - univ_poly = tup_to_univ_poly(part[0],x) - real_roots = univ_poly.roots(ring=AA,multiplicities=False) - assert real_roots, "No real solution exists... {} has no real roots".format(univ_poly) + univ_poly = tup_to_univ_poly(part[0],R) roots = univ_poly.roots(multiplicities=False) if roots: numeric_fvars[comp[0]] = roots[0] else: - non_cyclotomic_roots.append((comp[0],real_roots[0])) + #A real solution is preferred + roots = univ_poly.roots(ring=AA,multiplicities=False) + if not roots: + roots = univ_poly.roots(ring=QQbar,multiplicities=False) + non_cyclotomic_roots.append((comp[0],roots[0])) must_change_base_field = True #Otherwise, compute the component variety and select a point to obtain a numerical solution else: sols = self._get_component_variety(comp,part) - assert len(sols) > 1, "No real solution exists... component with variables {} has no real points".format(comp) + # assert len(sols) > 1, "No real solution exists... component with variables {} has no real points".format(comp) for fx, rhs in sols[0].items(): non_cyclotomic_roots.append((fx,rhs)) must_change_base_field = True @@ -1322,7 +1926,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): cyc_gen_as_bf_elt = bf_elts.pop(0) phi = self._FR.field().hom([cyc_gen_as_bf_elt], self._field) self._coerce_map_from_cyc_field = phi - numeric_fvars = { k : phi(v) for k, v in numeric_fvars.items() } + numeric_fvars = {k : phi(v) for k, v in numeric_fvars.items()} for i, elt in enumerate(bf_elts): numeric_fvars[non_cyclotomic_roots[i][0]] = elt @@ -1335,20 +1939,21 @@ def _get_explicit_solution(self,eqns=None,verbose=True): assert len(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self._solved)]) #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) - self._fvars = { sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items() } + self._fvars = {sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): - self._fvars[self._var_to_sextuple[self._poly_ring.gen(fx)]] = ((ETuple({},nvars),rhs),) - self._backward_subs() - self._fvars = { sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items() } + self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({},nvars),rhs),) + # self._backward_subs() + _backward_subs(self) + self._fvars = {sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items()} #Update base field attributes self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() - def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="",verbose=True,use_mp=True): + def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="",use_mp=True,verbose=True): r""" - Find an orthogonal solution to the pentagon equations associated to the - monoidal category represented by ``self``. + Solve the the hexagon and pentagon relations, along with + orthogonality constraints, to evaluate an orthogonal F-matrix. INPUT: @@ -1387,20 +1992,59 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" ``True`` is highly recommended, since parallel processing yields results much more quickly. + - ``verbose`` -- (default: ``True``) a boolean indicating whether the + solver should print out intermediate progress reports. + OUTPUT: This method returns ``None``. If the solver runs successfully, the results may be accessed through various methods, such as :meth:`get_fvars`, :meth:`fmatrix`, :meth:`fmat`, etc. - In many cases the F-symbols obtained are in fact real. In any case, the - F-symbols are obtained as elements of the associated + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B5",1), fusion_label="b", inject_variables=True) + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + sage: f.find_orthogonal_solution() + Computing F-symbols for The Fusion Ring of Type B5 and level 1 with Integer Ring coefficients with 14 variables... + Set up 25 hex and orthogonality constraints... + Partitioned 25 equations into 5 components of size: + [4, 3, 3, 3, 1] + Elimination epoch completed... 0 eqns remain in ideal basis + Hex elim step solved for 10 / 14 variables + Set up 7 reduced pentagons... + Elimination epoch completed... 0 eqns remain in ideal basis + Pent elim step solved for 12 / 14 variables + Partitioned 0 equations into 0 components of size: + [] + Partitioned 2 equations into 2 components of size: + [1, 1] + sage: f.fmatrix(b2, b2, b2, b2) + [ 1/2*zeta80^30 - 1/2*zeta80^10 -1/2*zeta80^30 + 1/2*zeta80^10] + [ 1/2*zeta80^30 - 1/2*zeta80^10 1/2*zeta80^30 - 1/2*zeta80^10] + sage: f.fmat(b2, b2, b2, b2, b0, b1) + -1/2*zeta80^30 + 1/2*zeta80^10 + + Every F-matrix `F^{a,b,c}_d` is orthogonal and in many cases real. + We may use :meth:`fmats_are_orthogonal` and :meth:`fvars_are_real` + to obtain correctness certificates. + + EXAMPLES:: + + sage: f.fmats_are_orthogonal() + True + sage: f.fvars_are_real() + True + + In any case, the F-symbols are obtained as elements of the associated :class:`FusionRing`'s :class:`CyclotomicField`, a computed - :class:`NumberField`, or ``QQbar``. Currently, the output field is - determined based on the ``CartanType`` associated to ``self``. - See :meth:`attempt_number_field_computation` for details. + :class:`NumberField`, or ``QQbar``. Currently, the field containing + the F-symbols is determined based on the ``CartanType`` associated + to ``self``. See :meth:`attempt_number_field_computation` for details. """ self._reset_solver_state() + if self._poly_ring.ngens() == 0: return #Resume computation from checkpoint if warm_start: @@ -1417,7 +2061,6 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) - poly_sortkey = poly_tup_sortkey_degrevlex if self._chkpt_status < 1: #Set up hexagon equations and orthogonality constraints self.get_orthogonality_constraints(output=False) @@ -1432,7 +2075,7 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" if self._chkpt_status < 2: #Set up equations graph. Find GB for each component in parallel. Eliminate variables self.ideal_basis = self._par_graph_gb(worker_pool=pool,verbose=verbose) - self.ideal_basis.sort(key=poly_sortkey) + self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(worker_pool=pool,verbose=verbose) #Update reduction parameters, also in children if any @@ -1447,7 +2090,7 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" if self._chkpt_status < 3: #Set up pentagon equations in parallel self.get_defining_equations('pentagons',worker_pool=pool,output=False) - self.ideal_basis.sort(key=poly_sortkey) + self.ideal_basis.sort(key=poly_tup_sortkey) #Report progress if verbose: @@ -1474,7 +2117,7 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" #Set up new equations graph and compute variety for each component if self._chkpt_status < 5: self.ideal_basis = self._par_graph_gb(term_order="lex",verbose=verbose) - self.ideal_basis.sort(key=poly_sortkey) + self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(verbose=verbose) self._checkpoint(checkpoint,5,verbose=verbose) @@ -1494,11 +2137,26 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" ### Cyclotomic method ### ######################### - def fix_gauge(self, algorithm=''): + def _fix_gauge(self, algorithm=""): """ Fix the gauge by forcing F-symbols not already fixed to equal 1. - This method should be used AFTER adding hex and pentagon eqns to - ``self.ideal_basis`` + + .. NOTE:: + + This method should be used AFTER adding hexagon and pentagon + equations to ``self.ideal_basis``. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A3",1)) # long time + sage: f._reset_solver_state() # long time + sage: eqns = f.get_defining_equations("hexagons")+f.get_defining_equations("pentagons") # long time + sage: f.ideal_basis = set(Ideal(eqns).groebner_basis()) # long time + sage: _, _ = f._substitute_degree_one() # long time + sage: f._fix_gauge() # long time + adding equation... fx1 - 1 + adding equation... fx18 - 1 + adding equation... fx21 - 1 """ while len(self._solved) < len(self._poly_ring.gens()): #Get a variable that has not been fixed @@ -1515,15 +2173,29 @@ def fix_gauge(self, algorithm=''): self._update_equations() def _substitute_degree_one(self, eqns=None): + """ + Substitute known value from linear univariate polynomial and + solve, following [Bond2007]_ p.37, for two-term linear equation + for one of the variables. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + creating variables fx1..fx27 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: f.ideal_basis = [fx0 - 8, fx4**2 - 3, fx4 + fx10 + 3, fx4 + fx9] + sage: _, _ = f._substitute_degree_one() + sage: f._fvars[f._var_to_sextuple[fx0]] + 8 + sage: f._fvars[f._var_to_sextuple[fx4]] + -fx9 + """ if eqns is None: eqns = self.ideal_basis new_knowns = set() useless = set() for eq in eqns: - #Substitute known value from univariate degree 1 polynomial or, - #Following Bonderson, p. 37, solve linear equation with two terms - #for one of the variables if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self._solved: self._fvars[self._var_to_sextuple[eq.lm()]] = -sum(c * m for c, m in zip(eq.coefficients()[1:], eq.monomials()[1:])) / eq.lc() #Add variable to set of known values and remove this equation @@ -1533,26 +2205,38 @@ def _substitute_degree_one(self, eqns=None): #Update fvars depending on other variables self._solved.update(new_knowns) for sextuple, rhs in self._fvars.items(): - d = { var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self._solved } + d = {var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self._solved} if d: self._fvars[sextuple] = rhs.subs(d) return new_knowns, useless def _update_equations(self): """ - Update ideal_basis equations by plugging in known values + Perform backward substitution on equations in ``self.ideal_basis``. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + creating variables fx1..fx27 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: f.ideal_basis = [fx0 - 8, fx4 + fx9, fx4**2 + fx3 - fx9**2] + sage: _, _ = f._substitute_degree_one() + sage: f._update_equations() + sage: f.ideal_basis + {fx3} """ - special_values = { known : self._fvars[self._var_to_sextuple[known]] for known in self._solved } + special_values = {known : self._fvars[self._var_to_sextuple[known]] for known in self._solved} self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) self.ideal_basis.discard(0) def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, output=False): """ Solve the the hexagon and pentagon relations to evaluate the F-matrix. + This method (omitting the orthogonality constraints) produces output in the cyclotomic field, but it is very limited in the size - of examples it can handle: for example, `A_2` at level 2 is - too large for this method. You may use :meth:`find_real_orthogonal_solution` + of examples it can handle: for example, `G_2` at level 2 is + too large for this method. You may use :meth:`find_orthogonal_solution` to solve much larger examples. INPUT: @@ -1567,7 +2251,7 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o EXAMPLES:: - sage: f=FMatrix(FusionRing("A2",1,fusion_labels="a",inject_variables=True),inject_variables=True) + sage: f = FMatrix(FusionRing("A2",1,fusion_labels="a",inject_variables=True),inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.find_cyclotomic_solution(output=True) @@ -1587,8 +2271,9 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o (a1, a1, a1, a0, a2, a2): 1} After you successfully run :meth:`find_cyclotomic_solution` you may - check the correctness of the F-matrix by running :meth:`hexagon` - and :meth:`pentagon`. These should return empty lists + check the correctness of the F-matrix by running + :meth:`get_defining_equations` with ``option='hexagons'`` and + ``option='pentagons'``. These should return empty lists of equations. EXAMPLES:: @@ -1600,6 +2285,7 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o """ self._reset_solver_state() + if self._poly_ring.ngens() == 0: return if equations is None: if verbose: @@ -1613,46 +2299,62 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o self._substitute_degree_one() if verbose: print("Fixing the gauge...") - self.fix_gauge(algorithm=algorithm) + self._fix_gauge(algorithm=algorithm) if verbose: print("Done!") if output: return self._fvars - ##################### ### Verifications ### ##################### - def verify_hexagons(self): - """ - Ensure the hexagon equations are satisfied + def certify_pentagons(self,use_mp=True,verbose=False): """ - hex = [] - for a,b,c,d,e,g in product(self._FR.basis(),repeat=6): - lhs = self._field(self._FR.r_matrix(a,c,e))*self.fmat(a,c,b,d,e,g)*self._field(self._FR.r_matrix(b,c,g)) - rhs = sum(self.fmat(c,a,b,d,e,f)*self._field(self._FR.r_matrix(f,c,d))*self.fmat(a,b,c,d,f,g) for f in self._FR.basis()) - hex.append(lhs-rhs) - if all(h == self._field.zero() for h in hex): - print("Success!!! Found valid F-symbols for {}".format(self._FR)) - else: - print("Ooops... something went wrong... These pentagons remain:") - print(hex) - return hex + Obtain a certificate of satisfaction for the pentagon equations, + up to floating-point error. - def verify_pentagons(self,use_mp=True,prune=False): - """ - Ensure the pentagon equations are satisfied + This method converts the computed F-symbols (available through + :meth:`get_fvars`) to native Python floats and then checks whether + the pentagon equations are satisfied using floating point arithmetic. + + When ``self.FR().basis()`` has many elements, verifying satisfaction + of the pentagon relations exactly using :meth:`get_defining_equations` + with ``option="pentagons"`` may take a long time. This method is + faster, but it cannot provide mathematical guarantees. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("C3",1)) # long time + sage: f.find_orthogonal_solution() # long time + Computing F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients with 71 variables... + Set up 134 hex and orthogonality constraints... + Partitioned 134 equations into 17 components of size: + [12, 12, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1] + Elimination epoch completed... 10 eqns remain in ideal basis + Elimination epoch completed... 0 eqns remain in ideal basis + Hex elim step solved for 51 / 71 variables + Set up 121 reduced pentagons... + Elimination epoch completed... 18 eqns remain in ideal basis + Elimination epoch completed... 5 eqns remain in ideal basis + Pent elim step solved for 64 / 71 variables + Partitioned 5 equations into 1 components of size: + [4] + Elimination epoch completed... 0 eqns remain in ideal basis + Partitioned 6 equations into 6 components of size: + [1, 1, 1, 1, 1, 1] + Computing appropriate NumberField... + sage: f.certify_pentagons() # long time + Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients """ - print("Testing F-symbols for {}...".format(self._FR)) fvars_copy = deepcopy(self._fvars) - self._fvars = { sextuple : float(RDF(rhs)) for sextuple, rhs in self.get_fmats_in_alg_field().items() } + self._fvars = {sextuple : float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} if use_mp: pool = Pool(processes=cpu_count()) else: pool = None n_proc = pool._processes if pool is not None else 1 - params = [(child_id,n_proc) for child_id in range(n_proc)] + params = [(child_id,n_proc,verbose) for child_id in range(n_proc)] pe = self._map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) if np.all(np.isclose(np.array(pe),0,atol=1e-7)): print("Success!!! Found valid F-symbols for {}".format(self._FR)) @@ -1665,10 +2367,39 @@ def verify_pentagons(self,use_mp=True,prune=False): def fmats_are_orthogonal(self): """ - Verify that all F-matrices are orthogonal + Verify that all F-matrices are orthogonal. + + This method should always return ``True`` when called after running + :meth:`find_orthogonal_solution`. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D4",1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: f.fmats_are_orthogonal() + True """ is_orthog = [] for a,b,c,d in product(self._FR.basis(),repeat=4): mat = self.fmatrix(a,b,c,d) is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) return all(is_orthog) + + def fvars_are_real(self): + """ + Test whether all F-symbols are real. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",3)) # long time + sage: f.find_orthogonal_solution(verbose=False) # long time + sage: f.fvars_are_real() # long time + True + """ + try: + for k, v in self._fvars.items(): + AA(self._qqbar_embedding(v)) + except ValueError: + print("The F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) + return False + return True diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index 9eebf2e5c58..2092478205b 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -1,10 +1,8 @@ -cpdef get_reduced_hexagons(factory, tuple mp_params) -cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=*) cpdef update_reduce(factory, tuple eq_tup) cpdef compute_gb(factory, tuple args) cpdef update_child_fmats(factory, tuple data_tup) -# cpdef pent_verify(factory, tuple mp_params) cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) -# cpdef update_reduce_chunk(factory, args) +cpdef executor(params) +cpdef collect_eqns(proc) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 9b8e013e93e..3030347ff8c 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -9,56 +9,135 @@ Fast FMatrix methods # **************************************************************************** cimport cython +from sage.combinat.root_system.poly_tup_engine cimport ( + compute_known_powers, + get_variables_degrees, variables, + poly_to_tup, _tup_to_poly, + subs, subs_squares, reduce_poly_dict, resize, + _flatten_coeffs, _unflatten_coeffs, + has_appropriate_linear_term, + resize +) +from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute +from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular +from sage.rings.polynomial.polydict cimport ETuple + import ctypes from itertools import product -from os import getpid -import sage -from sage.combinat.root_system.poly_tup_engine cimport * from sage.rings.ideal import Ideal -from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -# from sage.rings.qqbar import number_field_elements_from_algebraics - -#Define a global temporary worker results repository -cdef list worker_results = list() -############################## -### Parallel code executor ### -############################## +########################## +### Fast class methods ### +########################## -def executor(params): +cpdef _solve_for_linear_terms(factory,eqns=None): """ - Execute a function defined in this module (sage.combinat.root_system.fast_parallel_fmats_methods) - in a worker process, and supply the factory parameter by constructing a reference - to the FMatrix object in the worker's memory adress space from its id - ## NOTE: When the parent process is forked, each worker gets a copy of - every global variable. The virtual memory address of object X in the parent - process equals the VIRTUAL memory address of the copy of object X in each - worker, so we may construct references to forked copies of X using an id - obtained in the parent process - - TESTS: + Solve for a linear term occurring in a two-term equation, and for + variables appearing in univariate single-term equations. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + creating variables fx1..fx27 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _solve_for_linear_terms + sage: _solve_for_linear_terms(f) + True + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[0]]) + 0 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[2]]) + fx4 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[3]]) + fx3 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[4]]) + fx4 + """ + if eqns is None: + eqns = factory.ideal_basis - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor - sage: fmats = FMatrix(FusionRing("A1",3)) - sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) - sage: executor(params) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns - sage: len(collect_eqns(0)) == 63 + linear_terms_exist = False + for eq_tup in eqns: + #Only unflatten relevant polynomials + if len(eq_tup) > 2: + continue + eq_tup = _unflatten_coeffs(factory._field, eq_tup) + + if len(eq_tup) == 1: + vars = variables(eq_tup) + if len(vars) == 1 and vars[0] not in factory._solved: + factory._fvars[factory._idx_to_sextuple[vars[0]]] = tuple() + factory._solved.add(vars[0]) + linear_terms_exist = True + if len(eq_tup) == 2: + idx = has_appropriate_linear_term(eq_tup) + if idx < 0: continue + #The chosen term is guaranteed to be univariate in the largest variable + max_var = eq_tup[idx][0].nonzero_positions()[0] + if max_var not in factory._solved: + rhs_exp = eq_tup[(idx+1) % 2][0] + rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] + factory._fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) + factory._solved.add(max_var) + linear_terms_exist = True + return linear_terms_exist + +cpdef _backward_subs(factory): + """ + Perform backward substitution on ``self.ideal_basis``, traversing + variables in reverse lexicographical order. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + creating variables fx1..fx27 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _solve_for_linear_terms + sage: _solve_for_linear_terms(f) True - sage: fmats = FMatrix(FusionRing("E8",2)) - sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) - sage: executor(params) - sage: len(collect_eqns(0)) == 11 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[0]]) + 0 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[2]]) + fx4 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[3]]) + fx3 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[4]]) + fx4 + sage: f.ideal_basis.append(poly_to_tup(fx4-1)) + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: _solve_for_linear_terms(f) True + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _backward_subs + sage: _backward_subs(f) + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[0]]) + 0 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[2]]) + 1 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[3]]) + fx3 + sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[4]]) + 1 """ - (fn_name, fmats_id), args = params - #Construct a reference to global FMatrix object in this worker's memory - fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value - mod = sage.combinat.root_system.fast_parallel_fmats_methods - #Bind module method to FMatrix object in worker process, and call the method - return getattr(mod,fn_name)(fmats_obj,args) - + one = factory._field.one() + for var in reversed(factory._poly_ring.gens()): + sextuple = factory._var_to_sextuple[var] + rhs = factory._fvars[sextuple] + d = {var_idx : factory._fvars[factory._idx_to_sextuple[var_idx]] for var_idx in variables(rhs) if var_idx in factory._solved} + if d: + kp = compute_known_powers(get_variables_degrees([rhs]), d, one) + factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one),factory._ks).items()) ###################################### ### Fast fusion coefficients cache ### @@ -84,11 +163,13 @@ def executor(params): # # _Nk_ij = cached_function(_Nk_ij, name='_Nk_ij') - ############### ### Mappers ### ############### +#Define a global temporary worker results repository +cdef list worker_results = list() + cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): """ Cython version of fmat class method. Using cdef for fastest dispatch @@ -129,7 +210,7 @@ cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) -cpdef get_reduced_hexagons(factory, tuple mp_params): +cdef get_reduced_hexagons(factory, tuple mp_params): """ Set up and reduce the hexagon equations corresponding to this worker """ @@ -178,7 +259,7 @@ cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, id_anyon, zero, t @cython.wraparound(False) @cython.nonecheck(False) @cython.cdivision(True) -cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): +cdef get_reduced_pentagons(factory, tuple mp_params): """ Set up and reduce the pentagon equations corresponding to this worker """ @@ -201,9 +282,11 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): cdef MPolynomial_libsingular zero = factory._poly_ring.zero() #Computation loop - for i, nonuple in enumerate(product(basis,repeat=9)): + it = product(basis,repeat=9) + for i in range(len(basis)**9): + nonuple = next(it) if i % n_proc == child_id: - pe = feq_cy(basis,fvars,_Nk_ij,id_anyon,zero,nonuple,prune=prune) + pe = feq_cy(basis,fvars,_Nk_ij,id_anyon,zero,nonuple,prune=True) if pe: red = reduce_poly_dict(pe.dict(),_nnz,_ks,one) @@ -215,14 +298,34 @@ cpdef get_reduced_pentagons(factory, tuple mp_params, bint prune=True): cpdef update_reduce(factory, tuple eq_tup): """ Substitute known values, known squares, and reduce! + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B4",1), fusion_label="b", inject_variables=True) + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + sage: p = 2*fx0**2 - 3/7*fx0*fx1*fx3**3 + 5*fx0*fx4 + sage: from sage.combinat.root_system.poly_tup_engine import * + sage: f.ideal_basis = [poly_to_tup(p)] + sage: f._ks = {3: 2} + sage: f._var_degs = get_variables_degrees(f.ideal_basis) + sage: known_vals = {4: poly_to_tup(fx11**2)} + sage: f._nnz + (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 0, 0, 0, 0) + sage: f._kp = compute_known_powers(f._var_degs,known_vals,f._field.one()) + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import update_reduce, collect_eqns + sage: update_reduce(f, f.ideal_basis[0]) + sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs + sage: [f._tup_to_fpoly(_unflatten_coeffs(f._field,p)) for p in collect_eqns(0)] + [fx1*fx3 - 35/6*fx11^2 - 7/3*fx0] """ global worker_results cdef NumberFieldElement_absolute one = factory._field.one() #Construct cyclotomic field elts from list repn - eq_tup = _unflatten_coeffs(factory._field, eq_tup) + cdef tuple unflat = _unflatten_coeffs(factory._field, eq_tup) - cdef dict eq_dict = subs(eq_tup,factory._kp,one) + cdef dict eq_dict = subs(unflat,factory._kp,one) cdef tuple red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks,one) #Avoid pickling cyclotomic coefficients @@ -232,17 +335,48 @@ cpdef update_reduce(factory, tuple eq_tup): cpdef compute_gb(factory, tuple args): """ - Compute Groebner basis for given equations iterable + Compute the reduced Groebner basis for given equations iterable. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A2",2)) + sage: from multiprocessing import Pool, set_start_method + sage: try: + ....: set_start_method('fork') # context can be set only once + ....: except RuntimeError: + ....: pass + sage: f.get_defining_equations('hexagons',worker_pool=Pool(),output=False) + sage: partition = f._partition_eqns() + Partitioned 261 equations into 57 components of size: + [24, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1] + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import compute_gb, collect_eqns + sage: args = (partition[(29, 40, 83, 111, 148, 154)], "degrevlex") + sage: compute_gb(f, args) + sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs + sage: [f._tup_to_fpoly(_unflatten_coeffs(f._field,t)) for t in collect_eqns(0)] + [fx83*fx154 - fx111, + fx29^2*fx154 + fx29, + fx29*fx148 - fx40, + fx29*fx154^2 + fx154, + fx83*fx148 + fx29*fx154, + fx40*fx83 - fx29, + fx111*fx148 - fx154, + fx40*fx154 + fx148, + fx40*fx111 - fx29*fx154, + fx29*fx111 + fx83] """ global worker_results + cdef list eqns, sorted_vars eqns, term_order = args #Define smaller poly ring in component vars sorted_vars = list() + cdef tuple eq_tup + cdef int fx for eq_tup in eqns: for fx in variables(eq_tup): sorted_vars.append(fx) sorted_vars = sorted(set(sorted_vars)) - R = PolynomialRing(factory._FR.field(),len(sorted_vars),'a',order=term_order) + cdef MPolynomialRing_libsingular R = PolynomialRing(factory._FR.field(),len(sorted_vars),'a',order=term_order) #Zip tuples into R and compute Groebner basis cdef idx_map = { old : new for new, old in enumerate(sorted_vars) } @@ -251,7 +385,7 @@ cpdef compute_gb(factory, tuple args): cdef list polys = list() for eq_tup in eqns: eq_tup = _unflatten_coeffs(F, eq_tup) - polys.append(tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R)) + polys.append(_tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R)) gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") #Change back to fmats poly ring and append to temp_eqns @@ -271,6 +405,27 @@ cpdef update_child_fmats(factory, tuple data_tup): One-to-all communication used to update FMatrix object after each triangular elim step. We must update the algorithm's state values. These are: _fvars, _solved, _ks, _var_degs, _nnz, and _kp. + + TESTS:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: f.get_orthogonality_constraints(output=False) + sage: from multiprocessing import Pool, set_start_method + sage: try: + ....: set_start_method('fork') # context can be set only once + ....: except RuntimeError: + ....: pass + sage: pool = Pool() + sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) + sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f.mp_thresh = 0 + sage: f._triangular_elim(worker_pool=pool) # indirect doctest + Elimination epoch completed... 10 eqns remain in ideal basis + Elimination epoch completed... 0 eqns remain in ideal basis + sage: f.ideal_basis + [] """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object @@ -278,21 +433,32 @@ cpdef update_child_fmats(factory, tuple data_tup): factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) -# def get_appropriate_number_field(factory,non_cyclotomic_roots): -# """ -# If needed, find a NumberField containing the roots given roots -# """ -# roots = [factory._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] -# return number_field_elements_from_algebraics(roots,minimal=True) - ################ ### Reducers ### ################ -def collect_eqns(proc): +cpdef collect_eqns(proc): """ Helper function for returning processed results back to parent process. - Trivial reducer: simply collects objects with the same key in the worker + + Trivial reducer: simply collects objects with the same key in the worker. + This method is only useful when called after :meth:`executor`, whose + function argument appends output to the ``worker_results`` list. + + EXAMPLES:: + + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor + sage: fmats = FMatrix(FusionRing("A1",3)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) + sage: executor(params) + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns + sage: len(collect_eqns(0)) == 63 + True + sage: fmats = FMatrix(FusionRing("G2",2)) + sage: params = (('get_reduced_pentagons', id(fmats)), (0,1)) + sage: executor(params) + sage: len(collect_eqns(0)) == 4911 + True """ #Discard the zero polynomial global worker_results @@ -300,39 +466,102 @@ def collect_eqns(proc): worker_results = list() return list(reduced) -# #################### -# ### Verification ### -# #################### -# -# cdef feq_verif(factory, tuple nonuple, float tol=5e-8): -# """ -# Check the pentagon equation corresponding to the given nonuple -# """ -# global worker_results -# a, b, c, d, e, f, g, k, l = nonuple -# cdef float diff, lhs, rhs -# lhs = _fmat(factory,f,c,d,e,g,l)*_fmat(factory,a,b,l,e,f,k) -# rhs = 0.0 -# for h in factory._FR.basis(): -# rhs += _fmat(factory,a,b,c,g,f,h)*_fmat(factory,a,h,d,e,g,k)*_fmat(factory,b,c,d,k,h,l) -# diff = lhs - rhs -# if diff > tol or diff < -tol: -# worker_results.append(diff) -# -# @cython.wraparound(False) -# @cython.nonecheck(False) -# @cython.cdivision(True) -# cpdef pent_verify(factory, tuple mp_params): -# """ -# Generate all the pentagon equations assigned to this process, and reduce them -# """ -# child_id, n_proc = mp_params -# cdef float t0 -# cdef tuple nonuple -# cdef unsigned long i -# for i, nonuple in enumerate(product(factory._FR.basis(),repeat=9)): -# if i % n_proc == child_id: -# feq_verif(factory,nonuple) -# if i % 50000000 == 0 and i: -# print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) -# print("Ran through {} pentagons... Worker {} with pid {} reporting {} potential misses...".format(i,child_id,getpid(),len(worker_results))) +############################## +### Parallel code executor ### +############################## + +#Hard-coded module __dict__-style attribute with visible cdef methods +cdef dict mappers = { + "get_reduced_hexagons": get_reduced_hexagons, + "get_reduced_pentagons": get_reduced_pentagons, + "update_reduce": update_reduce, + "compute_gb": compute_gb, + "update_child_fmats": update_child_fmats, + "pent_verify": pent_verify + } + +cpdef executor(params): + r""" + Execute a function defined in this module + (``sage.combinat.root_system.fast_parallel_fmats_methods``) in a worker + process, and supply the factory parameter by constructing a reference + to the ``FMatrix`` object in the worker's memory adress space from + its ``id``. + + INPUT: + + - ``params`` -- a tuple ``((fn_name, fmats_id), fn_args)`` where + ``fn_name`` is the name of the function to be executed, ``fmats_id`` + is the ``id`` of the :class:`FMatrix` object, and ``fn_args`` is a + tuple containing all arguments to be passed to the function ``fn_name``. + + .. NOTES:: + + When the parent process is forked, each worker gets a copy of + every global variable. The virtual memory address of object `X` in + the parent process equals the *virtual* memory address of the copy + of object `X` in each worker, so we may construct references to + forked copies of `X` using an ``id`` obtained in the parent process. + + TESTS: + + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor + sage: fmats = FMatrix(FusionRing("A1",3)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) + sage: executor(params) + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns + sage: len(collect_eqns(0)) == 63 + True + sage: fmats = FMatrix(FusionRing("E8",2)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) + sage: executor(params) + sage: len(collect_eqns(0)) == 11 + True + """ + (fn_name, fmats_id), args = params + #Construct a reference to global FMatrix object in this worker's memory + fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value + #Bind module method to FMatrix object in worker process, and call the method + return mappers[fn_name](fmats_obj,args) + +#################### +### Verification ### +#################### + +cdef feq_verif(factory, fvars, Nk_ij, id_anyon, tuple nonuple, float tol=5e-8): + """ + Check the pentagon equation corresponding to the given nonuple + """ + global worker_results + a, b, c, d, e, f, g, k, l = nonuple + cdef float diff, lhs, rhs + + lhs = _fmat(fvars,Nk_ij,id_anyon,f,c,d,e,g,l)*_fmat(fvars,Nk_ij,id_anyon,a,b,l,e,f,k) + rhs = 0.0 + for h in factory._FR.basis(): + rhs += _fmat(fvars,Nk_ij,id_anyon,a,b,c,g,f,h)*_fmat(fvars,Nk_ij,id_anyon,a,h,d,e,g,k)*_fmat(fvars,Nk_ij,id_anyon,b,c,d,k,h,l) + diff = lhs - rhs + if diff > tol or diff < -tol: + worker_results.append(diff) + +@cython.wraparound(False) +@cython.nonecheck(False) +@cython.cdivision(True) +cdef pent_verify(factory, tuple mp_params): + """ + Generate all the pentagon equations assigned to this process, and reduce them + """ + child_id, n_proc, verbose = mp_params + cdef float t0 + cdef tuple nonuple + cdef unsigned long long i + + #Pre-compute common parameters for speed + Nk_ij = factory._FR.Nk_ij + cdef dict fvars = factory._fvars + id_anyon = factory._FR.one() + for i, nonuple in enumerate(product(factory._FR.basis(),repeat=9)): + if i % n_proc == child_id: + feq_verif(factory,fvars,Nk_ij,id_anyon,nonuple) + if i % 50000000 == 0 and i and verbose: + print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd index 134b6b57f2d..4dd122a7fbf 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd @@ -1,3 +1,3 @@ -cpdef sig_2k(fusion_ring, tuple args) -cpdef odd_one_out(fusion_ring, tuple args) cpdef _unflatten_entries(factory, list entries) +cpdef executor(params) +cpdef collect_results(proc) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 663249fd952..f6e8b40f1a7 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -8,62 +8,17 @@ Fast FusionRing methods for computing braid group representations # https://www.gnu.org/licenses/ # **************************************************************************** -cimport cython import ctypes -from itertools import product -import sage -from sage.combinat.root_system.poly_tup_engine cimport _flatten_coeffs, _unflatten_coeffs, poly_to_tup +cimport cython from sage.combinat.root_system.fast_parallel_fmats_methods cimport _fmat + +from itertools import product from sage.misc.cachefunc import cached_function from sage.rings.qqbar import QQbar #Define a global temporary worker results repository worker_results = list() -############################## -### Parallel code executor ### -############################## - -def executor(params): - """ - Execute a function defined in this module - (sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn) - in a worker process, and supply the `FusionRing` parameter by constructing a - reference to the FMatrix object in the worker's memory adress space - from its id. - - NOTES: - - When the parent process is forked, each worker gets a copy of - every global variable. The virtual memory address of object X in the parent - process equals the VIRTUAL memory address of the copy of object X in each - worker, so we may construct references to forked copies of X - - TESTS: - - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor - sage: FR = FusionRing("A1",4) - sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) - sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) - sage: executor(params) - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results - sage: len(collect_results(0)) == 13 - True - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor, collect_results - sage: FR = FusionRing("B2",2) - sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) - sage: params = (('odd_one_out',id(FR)),(0,1,(X,Xp,5))) - sage: executor(params) - sage: len(collect_results(0)) == 54 - True - """ - (fn_name, fr_id), args = params - #Construct a reference to global FMatrix object in this worker's memory - fusion_ring_obj = ctypes.cast(fr_id, ctypes.py_object).value - mod = sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn - #Bind module method to FMatrix object in worker process, and call the method - getattr(mod,fn_name)(fusion_ring_obj,args) - ############### ### Mappers ### ############### @@ -87,8 +42,32 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): (1/2, -1/2) sage: FR.get_computational_basis(one,two,4) [(two, two), (two, idd), (idd, two)] + sage: FR.fmats.find_orthogonal_solution(verbose=False) sage: mid_sig_ij(FR, (two, two), (two, idd), one, two) - (zeta48^10 - zeta48^2)*fx0*fx1*fx8 + (zeta48^2)*fx2*fx3*fx8 + 1/3*zeta48^10 - 2/3*zeta48^2 + + This method works for all possible types of fields returned by + ``self.fmats.field()``. + + TESTS:: + + sage: FR = FusionRing("A1",3) + sage: FR.fusion_labels("a",inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: _, _, to_opt = FR.fmats.field().optimized_representation() + sage: a2**4 + 2*a0 + 3*a2 + sage: FR.get_computational_basis(a2,a2,4) + [(a2, a2), (a2, a0), (a0, a2)] + sage: to_opt(mid_sig_ij(FR,(a2, a0),(a2, a2),a2,a2)) + -2024728666370660589/10956322398441542221*a1^30 - 34142146914395596291/21912644796883084442*a1^28 - 21479437628091413631/21912644796883084442*a1^26 + 260131910217202103829/21912644796883084442*a1^24 + 69575612911670713183/10956322398441542221*a1^22 + 25621808994337724689/1992058617898462222*a1^20 - 1975139725303994650417/21912644796883084442*a1^18 - 1315664901396537703585/21912644796883084442*a1^16 - 2421451803369354765026/10956322398441542221*a1^14 - 5963323855935165859057/21912644796883084442*a1^12 - 4477124943233705460859/21912644796883084442*a1^10 - 2001454824483021618178/10956322398441542221*a1^8 - 2120319455379289595185/21912644796883084442*a1^6 - 15722612944437234961/755608441271830498*a1^4 - 39862668562651453480/10956322398441542221*a1^2 - 6967145776903524195/10956322398441542221 + sage: FR = FusionRing("G2",2) + sage: FR.fusion_labels("g",inject_variables=True) + sage: FR.get_computational_basis(g1,g2,4) + [(g3, g2), (g3, g1), (g2, g3), (g2, g0), (g1, g3), (g1, g1), (g0, g2)] + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) + sage: mid_sig_ij(FR,(g2, g3),(g1, g1),g1,g2) # long time + -0.4566723195695565? + 0.0805236512828312?*I """ #Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars @@ -122,14 +101,38 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): EXAMPLES:: sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import odd_one_out_ij - sage: FR = FusionRing("B2",2) - sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) - sage: X.weight() - (1/2, 1/2) - sage: FR.get_computational_basis(X,X,3) - [(Y2,), (Y1,), (I0,)] - sage: odd_one_out_ij(FR,Y2,Y1,X,X) - (zeta40^10)*fx205*fx208 + (zeta40^14 - zeta40^10 + zeta40^6 - zeta40^2)*fx206*fx209 + (zeta40^2)*fx207*fx210 + sage: FR = FusionRing("A1",4) + sage: FR.fusion_labels(["one","two","three","four","five"],inject_variables=True) + sage: FR.get_computational_basis(two,two,5) + [(three, three, one), + (three, three, three), + (three, one, three), + (one, three, three), + (one, one, one)] + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: odd_one_out_ij(FR,one,three,two,two) + 2/3*zeta48^12 - 1/3*zeta48^8 - 1/3*zeta48^4 - 1/3 + + This method works for all possible types of fields returned by + ``self.fmats.field()``. + + TESTS:: + + sage: FR = FusionRing("A1",3) + sage: FR.fusion_labels("a",inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: _, _, to_opt = FR.fmats.field().optimized_representation() + sage: a2**3 + a0 + 2*a2 + sage: FR.get_computational_basis(a2,a2,3) + [(a2,), (a0,)] + sage: to_opt(odd_one_out_ij(FR,a0,a2,a2,a2)) + 6341990144855406911/21912644796883084442*a1^30 + 47313529044493641571/21912644796883084442*a1^28 - 6964289120109414595/10956322398441542221*a1^26 - 406719371329322780627/21912644796883084442*a1^24 + 87598732372849355687/10956322398441542221*a1^22 - 456724726845194775/19723352652460022*a1^20 + 3585892725441116840515/21912644796883084442*a1^18 - 645866255979227573282/10956322398441542221*a1^16 + 7958479159087829772639/21912644796883084442*a1^14 + 789748976956837633826/10956322398441542221*a1^12 + 3409710648897945752185/21912644796883084442*a1^10 + 903956381582048110980/10956322398441542221*a1^8 + 192973084151342020307/21912644796883084442*a1^6 - 9233312083438019435/755608441271830498*a1^4 + 667869266552877781/10956322398441542221*a1^2 + 17644302696056968099/21912644796883084442 + sage: FR = FusionRing("G2",2) + sage: FR.fusion_labels("g",inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) + sage: odd_one_out_ij(FR,g1,g2,g1,g1) # long time + -0.2636598866349343? + 0.4566723195695565?*I """ #Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars @@ -151,7 +154,7 @@ odd_one_out_ij = cached_function(odd_one_out_ij, name='odd_one_out_ij') @cython.nonecheck(False) @cython.cdivision(True) -cpdef sig_2k(fusion_ring, tuple args): +cdef sig_2k(fusion_ring, tuple args): """ Compute entries of the 2k-th braid generator """ @@ -204,7 +207,7 @@ cpdef sig_2k(fusion_ring, tuple args): #Avoid pickling cyclotomic field element objects if must_flatten_coeff: - entry = _flatten_entry(fusion_ring, entry) + entry = entry.list() worker_results.append(((basis_dict[nnz_pos],i), entry)) coords.add((basis_dict[nnz_pos],i)) @@ -218,17 +221,13 @@ cpdef sig_2k(fusion_ring, tuple args): #Avoid pickling cyclotomic field element objects if must_flatten_coeff: - #The entry is either a polynomial or a base field element - if entry.parent() == fusion_ring.fmats._poly_ring: - entry = _flatten_coeffs(poly_to_tup(entry)) - else: - entry = entry.list() + entry = entry.list() worker_results.append(((basis_dict[nnz_pos],i), entry)) @cython.nonecheck(False) @cython.cdivision(True) -cpdef odd_one_out(fusion_ring, tuple args): +cdef odd_one_out(fusion_ring, tuple args): """ Compute entries of the rightmost braid generator, in case we have an odd number of strands @@ -273,11 +272,7 @@ cpdef odd_one_out(fusion_ring, tuple args): #Avoid pickling cyclotomic field element objects if must_flatten_coeff: - #The entry is either a polynomial or a base field element - if entry.parent() == fusion_ring.fmats._poly_ring: - entry = _flatten_coeffs(poly_to_tup(entry)) - else: - entry = entry.list() + entry = entry.list() worker_results.append(((basis_dict[nnz_pos],i), entry)) continue @@ -295,7 +290,7 @@ cpdef odd_one_out(fusion_ring, tuple args): #Avoid pickling cyclotomic field element objects if must_flatten_coeff: - entry = _flatten_entry(fusion_ring, entry) + entry = entry.list() worker_results.append(((basis_dict[nnz_pos],i), entry)) @@ -303,10 +298,25 @@ cpdef odd_one_out(fusion_ring, tuple args): ### Reducers ### ################ -def collect_results(proc): +cpdef collect_results(proc): """ Helper function for returning processed results back to parent process. - Trivial reducer: simply collects objects with the same key in the worker + + Trivial reducer: simply collects objects with the same key in the worker. + This method is only useful when called after :meth:`executor`, whose + function argument appends output to the ``worker_results`` list. + + EXAMPLES:: + + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor + sage: FR = FusionRing("A1",4) + sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: params = (('sig_2k',id(FR)),(0,1,(2,one,one,9))) + sage: executor(params) + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results + sage: len(collect_results(0)) == 171 + True """ #Discard the zero polynomial global worker_results @@ -314,24 +324,60 @@ def collect_results(proc): worker_results = list() return reduced -###################################### -### Pickling circumvention helpers ### -###################################### +############################## +### Parallel code executor ### +############################## -cdef _flatten_entry(fusion_ring, entry): - """ - Flatten cyclotomic coefficients to a representation as a tuple of rational - coefficients. +#Hard-coded module __dict__-style attribute with visible cdef methods +cdef dict mappers = { + "sig_2k": sig_2k, + "odd_one_out": odd_one_out +} + +cpdef executor(params): + r""" + Execute a function registered in this module's ``mappers`` + in a worker process, and supply the ``FusionRing`` parameter by + constructing a reference to the FMatrix object in the worker's memory + adress space from its ``id``. + + .. NOTES:: + + When the parent process is forked, each worker gets a copy of + every global variable. The virtual memory address of object `X` in + the parent process equals the *virtual* memory address of the copy of + object `X` in each worker, so we may construct references to forked + copies of `X`. - This is used to avoid pickling cyclotomic coefficient objects, which fails - with new PARI settings introduced in trac ticket #30537 + TESTS:: + + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor + sage: FR = FusionRing("A1",4) + sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) + sage: executor(params) + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results + sage: len(collect_results(0)) == 13 + True + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor, collect_results + sage: FR = FusionRing("B2",2) + sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~23 s) + sage: params = (('odd_one_out',id(FR)),(0,1,(X,Xp,5))) + sage: executor(params) # long time + sage: len(collect_results(0)) == 54 # long time + True """ - #The entry is either a polynomial or a base field element - if entry.parent() == fusion_ring.fmats._poly_ring: - entry = _flatten_coeffs(poly_to_tup(entry)) - else: - entry = entry.list() - return entry + (fn_name, fr_id), args = params + #Construct a reference to global FMatrix object in this worker's memory + fusion_ring_obj = ctypes.cast(fr_id, ctypes.py_object).value + #Bind module method to FMatrix object in worker process, and call the method + mappers[fn_name](fusion_ring_obj,args) + +###################################### +### Pickling circumvention helpers ### +###################################### cpdef _unflatten_entries(fusion_ring, list entries): """ @@ -357,10 +403,4 @@ cpdef _unflatten_entries(fusion_ring, list entries): must_unflatten = F != QQbar if must_unflatten: for i, (coord, entry) in enumerate(entries): - #In this case entry represents a polynomial - if type(entry) == type(tuple()): - entry = fm.tup_to_fpoly(_unflatten_coeffs(F,entry)) - #Otherwise entry belongs to base field - else: - entry = F(entry) - entries[i] = (coord, entry) + entries[i] = (coord, F(entry)) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 1f3a11adbbf..3a033f56d16 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -15,7 +15,10 @@ from multiprocessing import Pool, set_start_method from sage.combinat.q_analogues import q_int import sage.combinat.root_system.f_matrix as FMatrix -from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results, executor, _unflatten_entries +from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import ( + collect_results, executor, + _unflatten_entries +) from sage.combinat.root_system.weyl_characters import WeylCharacterRing from sage.matrix.constructor import matrix from sage.matrix.special import diagonal_matrix @@ -24,7 +27,6 @@ from sage.misc.misc import inject_variable from sage.rings.integer_ring import ZZ from sage.rings.number_field.number_field import CyclotomicField - from sage.rings.qqbar import QQbar class FusionRing(WeylCharacterRing): @@ -374,26 +376,36 @@ def _test_total_q_order(self, **options): tester.assertTrue(tqo.is_real_positive()) tester.assertEqual(tqo**2, self.global_q_dimension(base_coercion=False)) - def test_braid_representation(self, **options): + def test_braid_representation(self, max_strands=6): """ Check that we can compute valid braid group representations. - This test indirectly partially verifies the correctness of the orthogonal - F-matrix solver. + INPUT: + + - ``max_strands`` -- (default: 6): maximum number of braid group strands + + Create a braid group representation using :meth:`get_braid_generators` + and confirms the braid relations. This test indirectly partially verifies + the correctness of the orthogonal F-matrix solver. If the code were + incorrect the method would not be deterministic because the fusing anyon + is chosen randomly. (A different choice is made for each number of strands tested.) + However the doctest is deterministic since it will always return True. EXAMPLES:: sage: F41 = FusionRing("F4",1) - sage: F41.test_braid_representation() + sage: F41.test_braid_representation(max_strands=4) + True sage: B22 = FusionRing("B2",2) # long time - sage: B22.test_braid_representation() # long time (~45s) + sage: B22.test_braid_representation() # long time + True """ if not self.is_multiplicity_free(): # Braid group representation is not available if self is not multiplicity free return True - tester = self._tester(**options) b = self.basis() + results = [] #Test with different numbers of strands - for n_strands in range(3,7): + for n_strands in range(3,max_strands+1): #Randomly select a fusing anyon. Skip the identity element, since #its braiding matrices are trivial while True: @@ -408,8 +420,9 @@ def test_braid_representation(self, **options): d = self(k) break comp_basis, sig = self.get_braid_generators(a,d,n_strands,verbose=False) - tester.assertTrue(len(comp_basis) > 0) - tester.assertTrue(self.gens_satisfy_braid_gp_rels(sig)) + results.append(len(comp_basis) > 0) + results.append(self.gens_satisfy_braid_gp_rels(sig)) + return all(results) def fusion_labels(self, labels=None, inject_variables=False): r""" @@ -537,7 +550,7 @@ def fvars_field(self): Cyclotomic Field of order 40 and degree 16 sage: a2**4 2*a0 + 3*a2 - sage: comp_basis, sig = A13.get_braid_generators(a2,a2,4,verbose=False) + sage: comp_basis, sig = A13.get_braid_generators(a2,a2,3,verbose=False) sage: A13.fvars_field() Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 sage: a2.q_dimension().parent() @@ -814,6 +827,37 @@ def s_ij(self, elt_i, elt_j, base_coercion=True): def s_ijconj(self, elt_i, elt_j, base_coercion=True): """ + Return the conjugate of the element of the S-matrix given by + ``self.s_ij(elt_i,elt_j,base_coercion=base_coercion)``. + + See :meth:`s_ij`. + + EXAMPLES:: + + sage: G21 = FusionRing("G2", 1) + sage: b = G21.basis() + sage: [G21.s_ijconj(x, y) for x in b for y in b] + [1, -zeta60^14 + zeta60^6 + zeta60^4, -zeta60^14 + zeta60^6 + zeta60^4, -1] + + This method works with all possible types of fields returned by + ``self.fmats.field()``. + + TESTS:: + + sage: E62 = FusionRing("E6",2) + sage: E62.fusion_labels("e", inject_variables=True) + sage: E62.s_ij(e8,e1).conjugate() == E62.s_ijconj(e8,e1) + True + sage: F41 = FusionRing("F4",1) + sage: F41.fmats.find_orthogonal_solution(verbose=False) + sage: b = F41.basis() + sage: all(F41.s_ijconj(x,y) == F41._basecoer(F41.s_ij(x,y,base_coercion=False).conjugate()) for x in b for y in b) + True + sage: G22 = FusionRing("G2",2) + sage: G22.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) + sage: b = G22.basis() + sage: all(G22.s_ijconj(x,y) == G22.fmats.field()(G22.s_ij(x,y,base_coercion=False).conjugate()) for x in b for y in b) + True """ ret = self.s_ij(elt_i, elt_j, base_coercion=False).conjugate() if (not base_coercion) or (self._basecoer is None): @@ -1035,36 +1079,36 @@ def is_multiplicity_free(self): ### Braid group representations ### ################################### - def get_trees(self,top_row,root): - """ - Recursively enumerate all the admissible trees with given top row and root. - - INPUT: - - - ``top_row`` -- a list of basis elements of self - - ``root`` -- a simple element of self - - Let `k` denote the length ``top_row``. This method returns - Returns a list of tuples `(l_1,...,l_{k-2})` such that - - .. MATH:: - - \\begin{array}{l} - root \\to l_{k-2} \otimes m_{k},\\ - l_{k-2} \\to l_{k-3} \otimes m_{k-1},\\ - \\cdots\\ - l_2\\to l_1\otimes m_3\\ - l_1\\to m_1\otimes m_2 - \end{array} - - where `a \\to b\otimes c` means `N_{bc}^a\\neq 0`. - """ - if len(top_row) == 2: - m1, m2 = top_row - return [[]] if self.Nk_ij(m1,m2,root) else [] - else: - m1, m2 = top_row[:2] - return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] + # def get_trees(self,top_row,root): + # """ + # Recursively enumerate all the admissible trees with given top row and root. + # + # INPUT: + # + # - ``top_row`` -- a list of basis elements of self + # - ``root`` -- a simple element of self + # + # Let `k` denote the length ``top_row``. This method returns + # Returns a list of tuples `(l_1,...,l_{k-2})` such that + # + # .. MATH:: + # + # \\begin{array}{l} + # root \\in l_{k-2} \otimes m_{k},\\ + # l_{k-2} \\in l_{k-3} \otimes m_{k-1},\\ + # \\cdots\\ + # l_2 \\in l_1\otimes m_3\\ + # l_1 \\in m_1\otimes m_2 + # \end{array} + # + # where `a \\to b\otimes c` means `N_{bc}^a\\neq 0`. + # """ + # if len(top_row) == 2: + # m1, m2 = top_row + # return [[]] if self.Nk_ij(m1,m2,root) else [] + # else: + # m1, m2 = top_row[:2] + # return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] def get_computational_basis(self,a,b,n_strands): """ @@ -1073,38 +1117,79 @@ def get_computational_basis(self,a,b,n_strands): INPUT: - ``a`` -- a basis element - - ``b`` -- another basis elements + - ``b`` -- another basis element - ``n_strands`` -- the number of strands for a braid group Let `n=` ``n_strands`` and let `k` be the greatest integer `\leqslant n/2`. The braid group acts on `\\text{Hom}(b,a^n)`. This action is computed in :meth:`get_braid_generators`. This method - returns the computational basis in the form of a set of - fusion trees. Each tree is represented by a `(n-2)`-tuple + returns the computational basis in the form of a list of + fusion trees. Each tree is represented by an `(n-2)`-tuple .. MATH:: - (m_1,\cdots,m_k,l_1,\cdots,l_{k-2}) + (m_1,\ldots,m_k,l_1,\ldots,l_{k-2}) + + such that each `m_j` is an irreducible constituent in `a \otimes a` + and + + .. MATH:: + + \\begin{array}{l} + b \\in l_{k-2} \otimes m_{k},\\\\ + l_{k-2} \\in l_{k-3} \otimes m_{k-1},\\\\ + \\cdots,\\\\ + l_2 \\in l_1\otimes m_3,\\\\ + l_1 \\in m_1\otimes m_2. + \end{array} + + where `z \\in x\otimes y` means `N_{xy}^z\\neq 0`. + + As a computational device when ``n_strands`` is odd, we pad the + vector `(m_1,\ldots,m_k)` with an additional `m_{k+1}` equal to `a`. + However, this `m_{k+1}` does *not* appear in the output of this method. + + The following example appears in Section 3.1 of [CW2015]_. + + EXAMPLES:: - These are computed by :meth:`get_trees`. As a computational - device when ``n_strands`` is odd, we pad the vector `(m_1,\cdots,m_k)` - with an additional `m_{k+1}` equal to `a` before passing it to - :meth:`get_trees`. This `m_{k+1}` does not appear in the output - of this method. + sage: A14 = FusionRing("A1",4) + sage: A14.get_order() + [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2)] + sage: A14.fusion_labels(["zero","one","two","three","four"],inject_variables=True) + sage: [A14(x) for x in A14.get_order()] + [zero, one, two, three, four] + sage: A14.get_computational_basis(one,two,4) + [(two, two), (two, zero), (zero, two)] """ + def _get_trees(fr,top_row,root): + if len(top_row) == 2: + m1, m2 = top_row + return [[]] if fr.Nk_ij(m1,m2,root) else [] + else: + m1, m2 = top_row[:2] + return [tuple([l,*b]) for l in fr.basis() for b in _get_trees(fr,[l]+top_row[2:],root) if fr.Nk_ij(m1,m2,l)] + comp_basis = list() for top in product((a*a).monomials(),repeat=n_strands//2): #If the n_strands is odd, we must extend the top row by a fusing anyon top_row = list(top)+[a]*(n_strands%2) - comp_basis.extend(tuple([*top,*levels]) for levels in self.get_trees(top_row,b)) + comp_basis.extend(tuple([*top,*levels]) for levels in _get_trees(self,top_row,b)) return comp_basis @lazy_attribute def fmats(self): """ Construct an FMatrix factory to solve the pentagon relations and - organize the resulting F-symbols. We only need this attribute to compute - braid group representations. + organize the resulting F-symbols. + + We only need this attribute to compute braid group representations. + + EXAMPLES:: + + sage: A15 = FusionRing("A1",5) + sage: A15.fmats + F-Matrix factory for The Fusion Ring of Type A1 and level 5 with Integer Ring coefficients """ return FMatrix.FMatrix(self) @@ -1120,7 +1205,7 @@ def _emap(self,mapper,input_args,worker_pool=None): ``fast_parallel_fusion_ring_braid_repn`` module. - ``input_args`` -- a tuple of arguments to be passed to mapper - NOTES: + .. NOTE:: If ``worker_pool`` is not provided, function maps and reduces on a single process. @@ -1129,6 +1214,19 @@ def _emap(self,mapper,input_args,worker_pool=None): input iterable. If it can't determine the length of the input iterable then it uses multiprocessing with the default chunksize of `1` if chunksize is not explicitly provided. + + EXAMPLES:: + + sage: FR = FusionRing("A1",4) + sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: len(FR._emap('sig_2k',(1,one,one,5))) # long time + 13 + sage: FR = FusionRing("A1",2) + sage: FR.fusion_labels("a",inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: len(FR._emap('odd_one_out',(a1,a1,7))) + 16 """ n_proc = worker_pool._processes if worker_pool is not None else 1 input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] @@ -1147,7 +1245,6 @@ def _emap(self,mapper,input_args,worker_pool=None): results = list() for worker_results in worker_pool.imap_unordered(collect_results,range(worker_pool._processes),chunksize=1): results.extend(worker_results) - # results = list(results) return results def get_braid_generators(self, @@ -1198,7 +1295,7 @@ def get_braid_generators(self, ``comp_basis`` is a list of basis elements of the braid group module, parametrized by a list of fusion ring elements describing a fusion tree. For example with 5 strands the fusion tree - is as follows. See :meth:`get_computational_basis` and :meth:`get_trees` + is as follows. See :meth:`get_computational_basis` for more information. .. image:: ../../../media/fusiontree.png @@ -1220,10 +1317,10 @@ def get_braid_generators(self, [one, two, three, four, five] sage: two ** 5 5*two + 4*four - sage: comp_basis, sig = A14.get_braid_generators(two,two,5,verbose=False) - sage: A14.gens_satisfy_braid_gp_rels(sig) + sage: comp_basis, sig = A14.get_braid_generators(two,two,5,verbose=False) # long time + sage: A14.gens_satisfy_braid_gp_rels(sig) # long time True - sage: len(comp_basis) == 5 + sage: len(comp_basis) == 5 # long time True """ @@ -1406,9 +1503,9 @@ def ribbon(self,base_coercion=True): sage: F = FusionRing("A1",3) sage: [x.twist() for x in F.basis()] [0, 3/10, 4/5, 3/2] - sage: [x.ribbon() for x in F.basis()] + sage: [x.ribbon(base_coercion=False) for x in F.basis()] [1, zeta40^6, zeta40^12 - zeta40^8 + zeta40^4 - 1, -zeta40^10] - sage: [F.root_of_unity(x) for x in [0, 3/10, 4/5, 3/2]] + sage: [F.root_of_unity(x,base_coercion=False) for x in [0, 3/10, 4/5, 3/2]] [1, zeta40^6, zeta40^12 - zeta40^8 + zeta40^4 - 1, -zeta40^10] """ ret = self.parent().root_of_unity(self.twist(),base_coercion=False) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 5eb7e11ad18..62f7401e7b0 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -1,24 +1,21 @@ -from sage.rings.polynomial.polydict cimport ETuple -from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute +from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular +from sage.rings.polynomial.polydict cimport ETuple cpdef tuple poly_to_tup(MPolynomial_libsingular poly) -cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) +cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) cpdef ETuple get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cpdef bint tup_fixes_sq(tuple eq_tup) -cpdef dict subs_squares(dict eq_dict, dict known_sq) +cdef dict subs_squares(dict eq_dict, dict known_sq) cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one) -cpdef dict subs(tuple poly_tup, dict known_powers, one) -# cpdef int poly_tup_cmp(tuple tleft, tuple tright) -cpdef tuple poly_tup_sortkey_degrevlex(tuple eq_tup) - -cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) -cpdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one) - - +cdef dict subs(tuple poly_tup, dict known_powers, one) +cpdef tup_to_univ_poly(tuple eq_tup, univ_poly_ring) +cpdef tuple poly_tup_sortkey(tuple eq_tup) +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one) cdef tuple _flatten_coeffs(tuple eq_tup) cpdef tuple _unflatten_coeffs(field, tuple eq_tup) +cdef int has_appropriate_linear_term(tuple eq_tup) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index fe4b6bf7799..2c7b034db1d 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -8,19 +8,11 @@ Arithmetic Engine for polynomials as tuples # https://www.gnu.org/licenses/ # **************************************************************************** -from functools import cmp_to_key -from sage.rings.polynomial.term_order import TermOrder - -# from sage.arith.functions cimport LCM_list - -#Pre-compute common values for speed -degrevlex_sortkey = TermOrder().sortkey_degrevlex - ########### ### API ### ########### -cpdef tuple poly_to_tup(MPolynomial_libsingular poly): +cpdef inline tuple poly_to_tup(MPolynomial_libsingular poly): """ Convert a polynomial object into the internal representation as tuple of (ETuple exp, NumberFieldElement coeff) pairs @@ -36,55 +28,55 @@ cpdef tuple poly_to_tup(MPolynomial_libsingular poly): """ return tuple(poly.dict().items()) -cpdef MPolynomial_libsingular tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent): - """ - Return a polynomial object from its tuple representation. Inverse of poly_to_tup. - poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup && tup_to_poly(poly_to_tup(eq), eq.parent()) == eq +cpdef inline MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent): + r""" + Return a polynomial object from its tuple of pairs representation. + + Inverse of :meth:`poly_to_tup`: + + ``poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup`` and + + ``tup_to_poly(poly_to_tup(eq), eq.parent()) == eq``. + + .. NOTE:: - NOTE: - Assumes parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup + Assumes ``parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup``. + + This method is meant for internal use. + + .. WARNING:: + + Unsafe for client use, since it avoids implicit casting and + it may lead to segmentation faults. EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import tup_to_poly - sage: R. = PolynomialRing(CyclotomicField(20)) - sage: poly_tup = (((2,0),1), ((0,0),1)) - sage: tup_to_poly(poly_tup, parent=R) + sage: from sage.combinat.root_system.poly_tup_engine import _tup_to_poly + sage: K = CyclotomicField(20) + sage: R. = PolynomialRing(K) + sage: poly_tup = (((2,0),K.one()), ((0,0),K.one())) + sage: _tup_to_poly(poly_tup, parent=R) x^2 + 1 sage: poly = x**2*y**4 - 4/5*x*y**2 + 1/3 * y sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: tup_to_poly(poly_to_tup(poly), parent=R) == poly + sage: _tup_to_poly(poly_to_tup(poly), parent=R) == poly True TESTS: - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, tup_to_poly + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, _tup_to_poly sage: R. = PolynomialRing(CyclotomicField(20)) sage: r = R.random_element() - sage: tup_to_poly(poly_to_tup(r), parent=R) == r + sage: _tup_to_poly(poly_to_tup(r), parent=R) == r True sage: R = PolynomialRing(QQ, 'fx', 100) sage: r = R.random_element() - sage: tup_to_poly(poly_to_tup(r), parent=R) == r + sage: _tup_to_poly(poly_to_tup(r), parent=R) == r True """ - # Maybe the following is faster but we need to ensure all coefficients are - # already in fmats._poly_ring.base_ring() so that implicit casting is avoided - # (this is pretty slow) - # return parent._element_constructor_(dict(eq_tup), check=False) - return parent(dict(eq_tup)) - -cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent): - r""" - Faster version of :meth:`tup_to_poly`. Unsafe for client use, since it - avoids implicit casting and it may lead to segmentation faults. - - Safe for internal use because our methods ensure that - polynomial coefficients always lie in the same base ring. - """ return parent._element_constructor_(dict(eq_tup), check=False) -cdef tuple _flatten_coeffs(tuple eq_tup): +cdef inline tuple _flatten_coeffs(tuple eq_tup): """ Flatten cyclotomic coefficients to a representation as a tuple of rational coefficients. @@ -93,6 +85,8 @@ cdef tuple _flatten_coeffs(tuple eq_tup): with new PARI settings introduced in trac ticket #30537 """ cdef list flat = list() + cdef ETuple exp + cdef NumberFieldElement_absolute cyc_coeff for exp, cyc_coeff in eq_tup: flat.append((exp, tuple(cyc_coeff._coefficients()))) return tuple(flat) @@ -119,63 +113,104 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): True """ cdef list unflat = list() + cdef ETuple exp for exp, coeff_tup in eq_tup: unflat.append((exp, field(list(coeff_tup)))) return tuple(unflat) +################################# +### Useful private predicates ### +################################# + +cdef inline int has_appropriate_linear_term(tuple eq_tup): + """ + Determine whether the given tuple of pairs (of length 2) contains + an *appropriate* linear term. + + In this context, a linear term is said to be *appropriate* if the + it is in the largest variable in the given polynomial (w.r.t. + the degrevlex ordering), the monomial in which the linear term + appears is univariate, and the linear term is not a common factor in + the polynomial. + + OUTPUT: + + If the given polynomial contains an appropriate linear term, this method + returns the index of the monomial in which the term appears. + + Otherwise, the method returns -1. + """ + max_var = variables(eq_tup)[0] + cdef ETuple m + for i in range(2): + m = eq_tup[i][0] + if m._nonzero == 1 and m._data[1] == 1 and m._data[0] == max_var and eq_tup[(i+1) % 2][0][max_var] == 0: + return i + return -1 + ###################### ### "Change rings" ### ###################### -def tup_to_univ_poly(tuple eq_tup, gen): +cpdef inline tup_to_univ_poly(tuple eq_tup, univ_poly_ring): """ Given a tuple of pairs representing a univariate polynomial and a univariate - polynomial ring generator, return a univariate polynomial object. + polynomial ring, return a univariate polynomial object. - Each pair in the tuple is assumed to be of the form (ETuple, coeff), where - coeff is an element of gen.parent().base_ring(). + Each pair in the tuple is assumed to be of the form ``(ETuple, coeff)``, + where ``coeff`` is an element of ``univ_poly_ring.base_ring()``. EXAMPLES:: sage: from sage.combinat.root_system.poly_tup_engine import tup_to_univ_poly sage: from sage.rings.polynomial.polydict import ETuple - sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) - sage: b = QQ['b'].gen() - sage: tup_to_univ_poly(poly_tup, b) + sage: K = CyclotomicField(56) + sage: poly_tup = ((ETuple([0,3,0]),K(2)), (ETuple([0,1,0]),K(-1)), (ETuple([0,0,0]),K(-2/3))) + sage: R = K['b'] + sage: tup_to_univ_poly(poly_tup, R) 2*b^3 - b - 2/3 - sage: poly_tup = ((ETuple([0, 0, 0]), -1/5),) - sage: tup_to_univ_poly(poly_tup, b) + + TESTS:: + + sage: poly_tup = ((ETuple([0, 0, 0]), K(-1/5)),) + sage: tup_to_univ_poly(poly_tup, R) -1/5 """ - univ_tup = tuple((exp.nonzero_values()[0] if exp.nonzero_values() else 0, c) for exp, c in eq_tup) - return sum(c * gen ** p for p, c in univ_tup) + cdef ETuple exp + cdef NumberFieldElement_absolute c + return univ_poly_ring({ exp._data[1] if exp._nonzero else 0 : c for exp, c in eq_tup }) -cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars): +cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): """ - Return a tuple representing a polynomial in a ring with len(sorted_vars) generators - This method is used for creating polynomial objects with the "right number" of - variables for computing Groebner bases of the partitioned equations graph - and for adding constraints ensuring certain F-symbols are nonzero + Return a tuple representing a polynomial in a ring with + ``len(sorted_vars)`` generators. + + This method is used for creating polynomial objects with the + "right number" of variables for computing Groebner bases of the + partitioned equations graph and for adding constraints ensuring certain + F-symbols are nonzero. EXAMPLES:: sage: from sage.combinat.root_system.poly_tup_engine import resize sage: from sage.rings.polynomial.polydict import ETuple - sage: poly_tup = ((ETuple([0,3,0,2]),2), (ETuple([0,1,0,1]),-1), (ETuple([0,0,0,0]),-2/3)) + sage: K = CyclotomicField(56) + sage: poly_tup = ((ETuple([0,3,0,2]),K(2)), (ETuple([0,1,0,1]),K(-1)), (ETuple([0,0,0,0]),K(-2/3))) sage: idx_map = { 1 : 0, 3 : 1 } sage: resize(poly_tup,idx_map,2) (((3, 2), 2), ((1, 1), -1), ((0, 0), -2/3)) - sage: R = PolynomialRing(ZZ, 'fx', 20) + sage: R = PolynomialRing(K, 'fx', 20) sage: R.inject_variables() Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19 - sage: sparse_poly = fx0**2 * fx17 + fx3 - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, tup_to_poly - sage: S. = PolynomialRing(ZZ) - sage: tup_to_poly(resize(poly_to_tup(sparse_poly),{0:0,3:1,17:2},3), parent=S) + sage: sparse_poly = R(fx0**2 * fx17 + fx3) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, _tup_to_poly + sage: S. = PolynomialRing(K) + sage: _tup_to_poly(resize(poly_to_tup(sparse_poly),{0:0,3:1,17:2},3), parent=S) x^2*z + y """ - cdef ETuple new_e + cdef ETuple exp, new_e + cdef NumberFieldElement_absolute c cdef list resized = list() for exp, c in eq_tup: new_e = ETuple({ idx_map[pos] : d for pos, d in exp.sparse_iter() }, nvars) @@ -186,7 +221,7 @@ cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars): ### Convenience methods ### ########################### -cdef ETuple degrees(tuple poly_tup): +cdef inline ETuple degrees(tuple poly_tup): """ Return the maximal degree of each variable in the polynomial """ @@ -241,7 +276,8 @@ cpdef list variables(tuple eq_tup): cpdef constant_coeff(tuple eq_tup): """ - Return the constant coefficient of the polynomial represented by given tuple + Return the constant coefficient of the polynomial represented by + given tuple. EXAMPLES:: @@ -263,15 +299,15 @@ cpdef constant_coeff(tuple eq_tup): cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): """ - Apply `coeff_map` to coefficients + Apply ``coeff_map`` to coefficients. EXAMPLES:: sage: from sage.combinat.root_system.poly_tup_engine import apply_coeff_map sage: sq = lambda x : x**2 - sage: R. = PolynomialRing(QQ) - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, tup_to_poly - sage: tup_to_poly(apply_coeff_map(poly_to_tup(x + 2*y + 3*z), sq), parent=R) + sage: R. = PolynomialRing(ZZ) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, _tup_to_poly + sage: _tup_to_poly(apply_coeff_map(poly_to_tup(x + 2*y + 3*z), sq), parent=R) x + 4*y + 9*z """ cdef ETuple exp @@ -280,10 +316,12 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): new_tup.append((exp, coeff_map(coeff))) return tuple(new_tup) -cpdef bint tup_fixes_sq(tuple eq_tup): - """ - Determine if given equation fixes the square of a variable. An equation - fixes the sq of a variable if it is of the form `a*x^2 + c` for *nonzero* constants `a`, `c` +cpdef inline bint tup_fixes_sq(tuple eq_tup): + r""" + Determine if given equation fixes the square of a variable. + + An equation fixes the sq of a variable if it is of the form `a*x^2 + c` + for *nonzero* constants `a`, `c`. EXAMPLES:: @@ -301,27 +339,38 @@ cpdef bint tup_fixes_sq(tuple eq_tup): False """ #Make this faster by combining two conditions into one... don't create temp variables - return len(eq_tup) == 2 and len(variables(eq_tup)) == 1 and eq_tup[0][0].nonzero_values() == [2] + # return len(eq_tup) == 2 and len(variables(eq_tup)) == 1 and eq_tup[0][0].nonzero_values() == [2] + # return len(eq_tup) == 2 and eq_tup[0][0].emax(eq_tup[1][0]).nonzero_values() == [2] + if len(eq_tup) != 2: + return False + #In order to access _attributes, we must cdef ETuple + cdef ETuple lm = eq_tup[0][0] + if lm._nonzero != 1 or lm._data[1] != 2: + return False + cdef ETuple tm = eq_tup[1][0] + if tm._nonzero != 0: + return False + return True ###################### ### Simplification ### ###################### -cpdef dict subs_squares(dict eq_dict, dict known_sq): - """ - Substitutes for known squares in a given polynomial. - The parameter known_sq is a dictionary of (int i, NumberFieldElement a) pairs such that x_i^2 - a == 0 - Returns a dictionary of (ETuple, coeff) pairs representing polynomial +cdef dict subs_squares(dict eq_dict, dict known_sq): + r""" + Substitute for known squares into a given polynomial. - EXAMPLES:: + INPUT: - sage: from sage.combinat.root_system.poly_tup_engine import subs_squares - sage: R. = PolynomialRing(QQ) - sage: poly = x**2 + y**3 + x*z**3 - sage: known_sq = { 0 : 2, 1 : -1, 2 : -1/2 } - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: subs_squares(dict(poly_to_tup(poly)), known_sq) - {(0, 0, 0): 2, (0, 1, 0): -1, (1, 0, 1): -1/2} + - ``eq_dict`` -- a dictionary of ``(ETuple, coeff)`` pairs representing + a polynomial. + + - ``known_sq`` -- a dictionary of ``(int i, NumberFieldElement a)`` pairs + such that `x_i^2 - a = 0`. + + OUTPUT: + + Returns a dictionary of ``(ETuple, coeff)`` pairs representing polynomial """ cdef dict subbed, new_e cdef ETuple exp, lm @@ -346,13 +395,17 @@ cpdef dict subs_squares(dict eq_dict, dict known_sq): return subbed cdef dict remove_gcf(dict eq_dict, ETuple nonz): - """ - Returns a dictionary of (ETuple, coeff) pairs describing the polynomial eq / GCF(eq) - The input nonz is an ETuple indicating the positions of variables known to be nonzero. - The entries of nonz are assumed to be some relatively large number, like 100 + r""" + Returns a dictionary of ``(ETuple, coeff)`` pairs describing the + polynomial ``eq / GCF(eq)``. + + The input ``nonz`` is an ``ETuple`` indicating the positions of + variables known to be nonzero. The entries of ``nonz`` are assumed to + be some relatively large number, like 100. """ #Find common variables, filtered according to known nonzeros cdef ETuple common_powers, exp + cdef NumberFieldElement_absolute c common_powers = nonz for exp, c in eq_dict.items(): common_powers = common_powers.emin(exp) @@ -363,26 +416,29 @@ cdef dict remove_gcf(dict eq_dict, ETuple nonz): cdef tuple to_monic(dict eq_dict, one): """ - Return tuple of pairs (ETuple, coeff) describing the monic polynomial associated to eq_dict - Here, the leading coefficient is chosen according to the degree reverse lexicographic ordering - (default for multivariate polynomial rings) + Return tuple of pairs ``(ETuple, coeff)`` describing the monic polynomial + associated to ``eq_dict``. + + Here, the leading coefficient is chosen according to the degree reverse + lexicographic ordering (default for multivariate polynomial rings). """ if not eq_dict: return tuple() - cdef list ord_monoms = sorted(eq_dict, key=degrevlex_sortkey) + cdef list ord_monoms = sorted(eq_dict, key=monom_sortkey) cdef ETuple lm = ord_monoms[-1] - lc = eq_dict[lm] + cdef NumberFieldElement_absolute lc = eq_dict[lm] if not lc: return tuple() cdef list ret = [(lm, one)] inv_lc = lc.inverse_of_unit() - cdef ETuple exp - for exp in reversed(ord_monoms[:-1]): - ret.append((exp, inv_lc * eq_dict[exp])) + cdef int i, n + n = len(ord_monoms) + for i in range(n-1): + ret.append((ord_monoms[n-2-i], inv_lc * eq_dict[ord_monoms[n-2-i]])) return tuple(ret) -cpdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one): +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one): """ - Return a dictionary describing a monic polynomial with no known nonzero gcd and - no known squares + Return a tuple describing a monic polynomial with no known nonzero + gcf and no known squares. """ if not eq_dict: return tuple() cdef dict sq_rmvd = subs_squares(eq_dict, known_sq) @@ -395,14 +451,17 @@ cpdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFie cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): """ - Pre-compute powers of known values for efficiency when preparing to substitute - into a list of polynomials. + Pre-compute powers of known values for efficiency when preparing to + substitute into a list of polynomials. INPUTS: - max_deg is an ETuple indicating the maximal degree of each variable - val_dict is a dictionary of (var_idx, poly_tup) key-value pairs. poly_tup - is a tuple of (ETuple, coeff) pairs reperesenting a multivariate polynomial + - ``max_deg`` -- an ``ETuple`` indicating the maximal degree of + each variable. + - ``val_dict`` -- a dictionary of ``(var_idx, poly_tup)`` key-value + pairs. + - ``poly_tup`` -- a tuple of (ETuple, coeff) pairs reperesenting a + multivariate polynomial. EXAMPLES:: @@ -436,25 +495,9 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): known_powers[var_idx][power+1] = tup_mul(known_powers[var_idx][power],val_dict[var_idx]) return known_powers -cpdef dict subs(tuple poly_tup, dict known_powers, one): +cdef dict subs(tuple poly_tup, dict known_powers, one): """ Substitute given variables into a polynomial tuple - - EXAMPLES:: - - sage: from sage.combinat.root_system.poly_tup_engine import subs - sage: R. = PolynomialRing(QQ) - sage: polys = [x**3 + 1, x**2*y + z**3, y**2 - 3*y] - sage: from sage.combinat.root_system.poly_tup_engine import compute_known_powers, get_variables_degrees, poly_to_tup - sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } - sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) - sage: poly_tup = poly_to_tup(polys[0]) - sage: one = R.base_ring().one() - sage: subs(poly_tup, compute_known_powers(max_deg, known_val, one), one) - {(0, 0, 0): 0} - sage: poly_tup = poly_to_tup(polys[1]) - sage: subs(poly_tup, compute_known_powers(max_deg, known_val, one), one) - {(0, 1, 0): 1, (0, 6, 0): 1} """ cdef dict subbed = {} cdef ETuple exp, m, shifted_exp @@ -494,106 +537,68 @@ cdef tuple tup_mul(tuple p1, tuple p2): ### Sorting ### ############### -#Implement richcmp comparator object that can be passed in as key to sorted method +cdef tuple monom_sortkey(ETuple exp): + """ + Produce a sortkey for a monomial exponent w.r.t. degree reversed + lexicographic ordering. + """ + cdef int deg = exp.unweighted_degree() + # for i in range(exp._nonzero): + # exp._data[2*i+1] = -exp._data[2*i+1] + cdef ETuple rev = exp.reversed().emul(-1) + return (deg, rev) -# cpdef int poly_tup_cmp(tuple tleft, tuple tright): -# """ -# Determine which polynomial is larger with respect to the degrevlex ordering -# -# EXAMPLES:: -# -# sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp -# sage: R. = PolynomialRing(QQ) -# sage: p1 = x*y*z - x**2 + 3/2 -# sage: p2 = x*y*z - x * y +1/2 -# sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup -# sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) -# True -# sage: R. = PolynomialRing(CyclotomicField(20)) -# sage: zeta20 = R.base_ring().gen() -# sage: p1 = zeta20**2 * x*z**2 - 2*zeta20 -# sage: p2 = y**3 + 1/4 -# sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) -# True -# -# TESTS: -# -# sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_cmp, poly_to_tup -# sage: R. = PolynomialRing(CyclotomicField(20)) -# sage: p1 = R.random_element() -# sage: p2 = R.random_element() -# sage: (p1 < p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) < 0) -# True -# sage: (p1 > p2) == (poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p2)) > 0) -# True -# sage: poly_tup_cmp(poly_to_tup(p1), poly_to_tup(p1)) == 0 -# True -# """ -# cdef int i, ret, sf, sg, val -# cdef ETuple f, g -# ret = 0 -# for i in range(min(len(tleft),len(tright))): -# f, g = tleft[i][0], tright[i][0] -# if f == g: -# if tleft[i][1] != tright[i][1]: -# ret = -1 + 2*(tleft[i][1] > tright[i][1]) -# else: -# sf, sg = 0, 0 -# for val in f.nonzero_values(sort=False): -# sf += val -# for val in g.nonzero_values(sort=False): -# sg += val -# ret = -1 + 2*(sf > sg or ( sf == sg and f.reversed() < g.reversed() )) -# if ret != 0: -# return ret -# return len(tleft) - len(tright) - -import numpy as np -cpdef tuple poly_tup_sortkey_degrevlex(tuple eq_tup): - """ - Return the sortkey of a polynomial represented as a tuple of (ETuple, coeff) - pairs with respect to the degree reversed lexicographical term order. +cpdef tuple poly_tup_sortkey(tuple eq_tup): + """ + Return the sortkey of a polynomial represented as a tuple of + ``(ETuple, coeff)`` pairs with respect to the degree + lexicographical term order. Using this key to sort polynomial tuples results in comparing polynomials term by term (we assume the tuple representation is sorted so that the - leading term with respect to the degree reverse lexicographical order comes - first). For each term, we first compare degrees, then the monomials themselves, - then the corresponding coefficient. + leading term with respect to the degree reverse lexicographical order + comes first). For each term, we first compare degrees, then the monomials + themselves. + + EXAMPLES:: - This method relies on the built-in comparison of ETuple's. + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: R. = PolynomialRing(QQ, order='deglex') + sage: p1 = x*y*z - x**2 + 3/2 + sage: p2 = x*y*z - x*y + 1/2 + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) + True + sage: R. = PolynomialRing(CyclotomicField(20), order='deglex') + sage: zeta20 = R.base_ring().gen() + sage: p1 = zeta20**2 * x*z**2 - 2*zeta20 + sage: p2 = y**3 + 1/4 + sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) + True - EXAMPLES :: + TESTS: + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup + sage: R. = PolynomialRing(CyclotomicField(20)) + sage: p1 = R.random_element() + sage: p2 = R.random_element() + sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) + True + sage: (p1 > p2) == (poly_tup_sortkey(poly_to_tup(p1)) > poly_tup_sortkey(poly_to_tup(p2))) + True + sage: poly_tup_sortkey(poly_to_tup(p1)) == poly_tup_sortkey(poly_to_tup(p1)) + True """ cdef ETuple exp cdef int i, l, nnz - key = list() - # for exp, c in eq_tup: - # key.extend([sum(exp.nonzero_values(sort=False)),exp.reversed().emul(-1),c._coefficients()]) - # for exp, c in eq_tup: - # #Compare by term degree - # key.append(exp.unweighted_degree()) - # revlex_exp = exp.reversed().emul(-1) - # #Then by term w.r.t. revlex order - # revlex_exp_key = list() - # for i from 0 <= i < exp._nonzero: - # #Reverse tuple and negate values - # # key.append(-(exp._length - exp._data[2*(exp._nonzero-i-1)] - 1)) - # # key.append(-exp._data[2*(exp._nonzero-i-1)+1]) - # key.append(-exp._data[2*i]) - # key.append(exp._data[2*i+1]) - # #Finally by coefficient - # key.extend(c._coefficients()) + cdef list key = list() for exp, c in eq_tup: #Compare by term degree key.append(exp.unweighted_degree()) - #Next compare by term w.r.t. revlex order - l = exp._length - nnz = exp._nonzero - for i from 0 <= i < nnz: - # key.append(l-1-exp._data[2*(nnz-i-1)]) + #Next compare by term w.r.t. lex order + for i in range(exp._nonzero): + # key.append(exp._length-1-exp._data[2*(nnz-i-1)]) # key.append(-exp._data[2*(nnz-i-1)+1]) - # Try sorting in lex order instead key.append(-exp._data[2*i]) key.append(exp._data[2*i+1]) return tuple(key) From 7c4ecb21a043bf6ccaefe692e9aed876bf85fe69 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Tue, 6 Apr 2021 20:22:12 -0700 Subject: [PATCH 035/414] make tests in fast_parallel_fusion_ring_braid_repn.pyx long to avoid excessive test time. --- .../fast_parallel_fusion_ring_braid_repn.pyx | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index f6e8b40f1a7..c02f49588d0 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -36,14 +36,14 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): EXAMPLES:: sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import mid_sig_ij - sage: FR = FusionRing("A1",4) - sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) - sage: one.weight() + sage: FR = FusionRing("A1",4) # long time + sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) # long time + sage: one.weight() # long time (1/2, -1/2) - sage: FR.get_computational_basis(one,two,4) + sage: FR.get_computational_basis(one,two,4) # long time [(two, two), (two, idd), (idd, two)] - sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: mid_sig_ij(FR, (two, two), (two, idd), one, two) + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~7s) + sage: mid_sig_ij(FR, (two, two), (two, idd), one, two) # long time 1/3*zeta48^10 - 2/3*zeta48^2 This method works for all possible types of fields returned by @@ -51,19 +51,19 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): TESTS:: - sage: FR = FusionRing("A1",3) - sage: FR.fusion_labels("a",inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: _, _, to_opt = FR.fmats.field().optimized_representation() - sage: a2**4 + sage: FR = FusionRing("A1",3) # long time + sage: FR.fusion_labels("a",inject_variables=True) # long time + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: _, _, to_opt = FR.fmats.field().optimized_representation() # long time + sage: a2**4 # long time 2*a0 + 3*a2 - sage: FR.get_computational_basis(a2,a2,4) + sage: FR.get_computational_basis(a2,a2,4) # long time [(a2, a2), (a2, a0), (a0, a2)] - sage: to_opt(mid_sig_ij(FR,(a2, a0),(a2, a2),a2,a2)) + sage: to_opt(mid_sig_ij(FR,(a2, a0),(a2, a2),a2,a2)) # long time -2024728666370660589/10956322398441542221*a1^30 - 34142146914395596291/21912644796883084442*a1^28 - 21479437628091413631/21912644796883084442*a1^26 + 260131910217202103829/21912644796883084442*a1^24 + 69575612911670713183/10956322398441542221*a1^22 + 25621808994337724689/1992058617898462222*a1^20 - 1975139725303994650417/21912644796883084442*a1^18 - 1315664901396537703585/21912644796883084442*a1^16 - 2421451803369354765026/10956322398441542221*a1^14 - 5963323855935165859057/21912644796883084442*a1^12 - 4477124943233705460859/21912644796883084442*a1^10 - 2001454824483021618178/10956322398441542221*a1^8 - 2120319455379289595185/21912644796883084442*a1^6 - 15722612944437234961/755608441271830498*a1^4 - 39862668562651453480/10956322398441542221*a1^2 - 6967145776903524195/10956322398441542221 - sage: FR = FusionRing("G2",2) - sage: FR.fusion_labels("g",inject_variables=True) - sage: FR.get_computational_basis(g1,g2,4) + sage: FR = FusionRing("G2",2) # long time + sage: FR.fusion_labels("g",inject_variables=True) # long time + sage: FR.get_computational_basis(g1,g2,4) # long time [(g3, g2), (g3, g1), (g2, g3), (g2, g0), (g1, g3), (g1, g1), (g0, g2)] sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) sage: mid_sig_ij(FR,(g2, g3),(g1, g1),g1,g2) # long time @@ -101,16 +101,16 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): EXAMPLES:: sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import odd_one_out_ij - sage: FR = FusionRing("A1",4) - sage: FR.fusion_labels(["one","two","three","four","five"],inject_variables=True) - sage: FR.get_computational_basis(two,two,5) + sage: FR = FusionRing("A1",4) # long time + sage: FR.fusion_labels(["one","two","three","four","five"],inject_variables=True) # long time + sage: FR.get_computational_basis(two,two,5) # long time [(three, three, one), (three, three, three), (three, one, three), (one, three, three), (one, one, one)] - sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: odd_one_out_ij(FR,one,three,two,two) + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: odd_one_out_ij(FR,one,three,two,two) # long time 2/3*zeta48^12 - 1/3*zeta48^8 - 1/3*zeta48^4 - 1/3 This method works for all possible types of fields returned by @@ -118,18 +118,18 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): TESTS:: - sage: FR = FusionRing("A1",3) - sage: FR.fusion_labels("a",inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: _, _, to_opt = FR.fmats.field().optimized_representation() - sage: a2**3 + sage: FR = FusionRing("A1",3) # long time + sage: FR.fusion_labels("a",inject_variables=True) # long time + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: _, _, to_opt = FR.fmats.field().optimized_representation() # long time + sage: a2**3 # long time a0 + 2*a2 - sage: FR.get_computational_basis(a2,a2,3) + sage: FR.get_computational_basis(a2,a2,3) # long time [(a2,), (a0,)] - sage: to_opt(odd_one_out_ij(FR,a0,a2,a2,a2)) + sage: to_opt(odd_one_out_ij(FR,a0,a2,a2,a2)) # long time 6341990144855406911/21912644796883084442*a1^30 + 47313529044493641571/21912644796883084442*a1^28 - 6964289120109414595/10956322398441542221*a1^26 - 406719371329322780627/21912644796883084442*a1^24 + 87598732372849355687/10956322398441542221*a1^22 - 456724726845194775/19723352652460022*a1^20 + 3585892725441116840515/21912644796883084442*a1^18 - 645866255979227573282/10956322398441542221*a1^16 + 7958479159087829772639/21912644796883084442*a1^14 + 789748976956837633826/10956322398441542221*a1^12 + 3409710648897945752185/21912644796883084442*a1^10 + 903956381582048110980/10956322398441542221*a1^8 + 192973084151342020307/21912644796883084442*a1^6 - 9233312083438019435/755608441271830498*a1^4 + 667869266552877781/10956322398441542221*a1^2 + 17644302696056968099/21912644796883084442 - sage: FR = FusionRing("G2",2) - sage: FR.fusion_labels("g",inject_variables=True) + sage: FR = FusionRing("G2",2) # long time + sage: FR.fusion_labels("g",inject_variables=True) # long time sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) sage: odd_one_out_ij(FR,g1,g2,g1,g1) # long time -0.2636598866349343? + 0.4566723195695565?*I @@ -309,13 +309,13 @@ cpdef collect_results(proc): EXAMPLES:: sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor - sage: FR = FusionRing("A1",4) - sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: params = (('sig_2k',id(FR)),(0,1,(2,one,one,9))) - sage: executor(params) + sage: FR = FusionRing("A1",4) # long time + sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) # long time + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: params = (('sig_2k',id(FR)),(0,1,(2,one,one,9))) # long time + sage: executor(params) # long time sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results - sage: len(collect_results(0)) == 171 + sage: len(collect_results(0)) == 171 # long time True """ #Discard the zero polynomial @@ -354,17 +354,17 @@ cpdef executor(params): sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor sage: FR = FusionRing("A1",4) sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) - sage: executor(params) + sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) # long time + sage: executor(params) # long time sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results - sage: len(collect_results(0)) == 13 + sage: len(collect_results(0)) == 13 # long time True sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor, collect_results - sage: FR = FusionRing("B2",2) - sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) + sage: FR = FusionRing("B2",2) # long time + sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) # long time sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~23 s) - sage: params = (('odd_one_out',id(FR)),(0,1,(X,Xp,5))) + sage: params = (('odd_one_out',id(FR)),(0,1,(X,Xp,5))) # long time sage: executor(params) # long time sage: len(collect_results(0)) == 54 # long time True From da6c2021b4305196e595c76eed8fdd453d2e21f9 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 7 Apr 2021 13:42:54 -0400 Subject: [PATCH 036/414] shorter doctests in f_matrix.py --- src/sage/combinat/root_system/f_matrix.py | 69 +++++++++---------- .../fast_parallel_fmats_methods.pyx | 2 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 2 +- src/sage/combinat/root_system/fusion_ring.py | 31 --------- .../combinat/root_system/poly_tup_engine.pyx | 4 +- 5 files changed, 38 insertions(+), 70 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index a7d276c2a0d..48872ce5480 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -44,7 +44,6 @@ from sage.rings.polynomial.all import PolynomialRing from sage.rings.polynomial.polydict import ETuple from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics -from sage.rings.real_double import RDF class FMatrix(): def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): @@ -362,7 +361,7 @@ def _reset_solver_state(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("F4",1)) + sage: f = FMatrix(FusionRing("G2",1)) sage: K = f.field() sage: len(f._nnz.nonzero_positions()) 1 @@ -542,15 +541,15 @@ def field(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("F4",1,conjugate=True)) + sage: f = FMatrix(FusionRing("G2",1)) sage: f.field() - Cyclotomic Field of order 80 and degree 32 + Cyclotomic Field of order 60 and degree 16 sage: f.find_orthogonal_solution(verbose=False) sage: f.field() - Number Field in a with defining polynomial y^64 - 16*y^62 + 104*y^60 - 320*y^58 + 258*y^56 + 1048*y^54 - 2864*y^52 - 3400*y^50 + 47907*y^48 - 157616*y^46 + 301620*y^44 - 322648*y^42 + 2666560*y^40 + 498040*y^38 + 54355076*y^36 - 91585712*y^34 + 592062753*y^32 - 1153363592*y^30 + 3018582788*y^28 - 4848467552*y^26 + 7401027796*y^24 - 8333924904*y^22 + 8436104244*y^20 - 7023494736*y^18 + 4920630467*y^16 - 2712058560*y^14 + 1352566244*y^12 - 483424648*y^10 + 101995598*y^8 - 12532920*y^6 + 1061168*y^4 - 57864*y^2 + 1681 + Number Field in a with defining polynomial y^32 - 6*y^30 - 7*y^28 + 62*y^26 - 52*y^24 - 308*y^22 + 831*y^20 + 7496*y^18 + 18003*y^16 - 2252*y^14 + 42259*y^12 - 65036*y^10 + 29368*y^8 - 3894*y^6 + 377*y^4 - 22*y^2 + 1 sage: phi = f.get_qqbar_embedding() sage: [phi(r).n() for r in f.get_non_cyclotomic_roots()] - [-0.786151377757423 + 1.73579267033929e-59*I] + [-0.786151377757423 - 8.92806368517581e-31*I] .. NOTE:: @@ -750,12 +749,13 @@ def get_non_cyclotomic_roots(self): True sage: f.get_non_cyclotomic_roots() [] - sage: f = FMatrix(FusionRing("E7",2)) # long time - sage: f.find_orthogonal_solution(verbose=False) # long time - sage: f.field() == f.FR().field() # long time + sage: f = FMatrix(FusionRing("F4",1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: f.field() == f.FR().field() False - sage: f.get_non_cyclotomic_roots() # long time - [-0.7861513777574233?, -0.5558929702514212?] + sage: phi = f.get_qqbar_embedding() + sage: [phi(r).n() for r in f.get_non_cyclotomic_roots()] + [-0.786151377757423 + 1.73579267033929e-59*I] When ``self.field()`` is a ``NumberField``, one may use :meth:`get_qqbar_embedding` to embed the resulting values into @@ -881,8 +881,8 @@ def get_radical_expression(self): sage: f = FMatrix(FusionRing("G2",1)) sage: f.FR().fusion_labels("g", inject_variables=True) sage: f.find_orthogonal_solution(verbose=False) - sage: radical_fvars = f.get_radical_expression() - sage: radical_fvars[g1, g1, g1, g1, g1, g0] + sage: radical_fvars = f.get_radical_expression() # long time (~1.5s) + sage: radical_fvars[g1, g1, g1, g1, g1, g0] # long time -sqrt(1/2*sqrt(5) - 1/2) """ return {sextuple : val.radical_expression() for sextuple, val in self.get_fvars_in_alg_field().items()} @@ -1202,7 +1202,6 @@ def _checkpoint(self,do_chkpt,status,verbose=True): return filename = "fmatrix_solver_checkpoint_" + self.get_fr_str() + ".pickle" with open(filename, 'wb') as f: - # pickle.dump([self._fvars, self._solved, eqns, status], f) pickle.dump([self._fvars, self._solved, self._ks, self.ideal_basis, status], f) if verbose: print(f"Checkpoint {status} reached!") @@ -1214,7 +1213,7 @@ def _restore_state(self,filename): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",3)) + sage: f = FMatrix(FusionRing("A1",2)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons',output=False) @@ -1231,8 +1230,8 @@ def _restore_state(self,filename): sage: f._checkpoint(do_chkpt=True,status=2) Checkpoint 2 reached! sage: del f - sage: f = FMatrix(FusionRing("A1",3)) - sage: f._restore_state("fmatrix_solver_checkpoint_A13.pickle") + sage: f = FMatrix(FusionRing("A1",2)) + sage: f._restore_state("fmatrix_solver_checkpoint_A12.pickle") sage: fvars == f._fvars True sage: ib == f.ideal_basis @@ -1243,7 +1242,7 @@ def _restore_state(self,filename): True sage: 2 == f._chkpt_status True - sage: os.remove("fmatrix_solver_checkpoint_A13.pickle") + sage: os.remove("fmatrix_solver_checkpoint_A12.pickle") TESTS:: @@ -1843,27 +1842,27 @@ def _get_explicit_solution(self,eqns=None,verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2",2)) # indirect doctest + sage: f = FMatrix(FusionRing("A1",4)) # indirect doctest sage: f.find_orthogonal_solution() # long time - Computing F-symbols for The Fusion Ring of Type A2 and level 2 with Integer Ring coefficients with 287 variables... - Set up 548 hex and orthogonality constraints... - Partitioned 548 equations into 57 components of size: - [24, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1] - Elimination epoch completed... 53 eqns remain in ideal basis + Computing F-symbols for The Fusion Ring of Type A1 and level 4 with Integer Ring coefficients with 238 variables... + Set up 460 hex and orthogonality constraints... + Partitioned 460 equations into 41 components of size: + [24, 12, 12, 12, 12, 12, 12, 12, 12, 9, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1] + Elimination epoch completed... 63 eqns remain in ideal basis Elimination epoch completed... 5 eqns remain in ideal basis Elimination epoch completed... 0 eqns remain in ideal basis - Hex elim step solved for 203 / 287 variables - Set up 994 reduced pentagons... - Elimination epoch completed... 699 eqns remain in ideal basis - Elimination epoch completed... 279 eqns remain in ideal basis - Elimination epoch completed... 9 eqns remain in ideal basis - Pent elim step solved for 270 / 287 variables - Partitioned 9 equations into 1 components of size: - [5] + Hex elim step solved for 173 / 238 variables + Set up 888 reduced pentagons... + Elimination epoch completed... 562 eqns remain in ideal basis + Elimination epoch completed... 209 eqns remain in ideal basis + Elimination epoch completed... 26 eqns remain in ideal basis + Elimination epoch completed... 8 eqns remain in ideal basis Elimination epoch completed... 0 eqns remain in ideal basis - Partitioned 16 equations into 16 components of size: - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - Computing appropriate NumberField... + Pent elim step solved for 225 / 238 variables + Partitioned 0 equations into 0 components of size: + [] + Partitioned 13 equations into 13 components of size: + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] """ if eqns is None: eqns = self.ideal_basis diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 3030347ff8c..c2afc4b240c 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -495,7 +495,7 @@ cpdef executor(params): is the ``id`` of the :class:`FMatrix` object, and ``fn_args`` is a tuple containing all arguments to be passed to the function ``fn_name``. - .. NOTES:: + .. NOTE:: When the parent process is forked, each worker gets a copy of every global variable. The virtual memory address of object `X` in diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index c02f49588d0..a40a5f136c1 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -341,7 +341,7 @@ cpdef executor(params): constructing a reference to the FMatrix object in the worker's memory adress space from its ``id``. - .. NOTES:: + .. NOTE:: When the parent process is forked, each worker gets a copy of every global variable. The virtual memory address of object `X` in diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 3a033f56d16..c402fd6b775 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -1079,37 +1079,6 @@ def is_multiplicity_free(self): ### Braid group representations ### ################################### - # def get_trees(self,top_row,root): - # """ - # Recursively enumerate all the admissible trees with given top row and root. - # - # INPUT: - # - # - ``top_row`` -- a list of basis elements of self - # - ``root`` -- a simple element of self - # - # Let `k` denote the length ``top_row``. This method returns - # Returns a list of tuples `(l_1,...,l_{k-2})` such that - # - # .. MATH:: - # - # \\begin{array}{l} - # root \\in l_{k-2} \otimes m_{k},\\ - # l_{k-2} \\in l_{k-3} \otimes m_{k-1},\\ - # \\cdots\\ - # l_2 \\in l_1\otimes m_3\\ - # l_1 \\in m_1\otimes m_2 - # \end{array} - # - # where `a \\to b\otimes c` means `N_{bc}^a\\neq 0`. - # """ - # if len(top_row) == 2: - # m1, m2 = top_row - # return [[]] if self.Nk_ij(m1,m2,root) else [] - # else: - # m1, m2 = top_row[:2] - # return [tuple([l,*b]) for l in self.basis() for b in self.get_trees([l]+top_row[2:],root) if self.Nk_ij(m1,m2,l)] - def get_computational_basis(self,a,b,n_strands): """ Return the so-called computational basis for `\\text{Hom}(b, a^n)`. diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 2c7b034db1d..24baf59fd46 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -370,7 +370,7 @@ cdef dict subs_squares(dict eq_dict, dict known_sq): OUTPUT: - Returns a dictionary of ``(ETuple, coeff)`` pairs representing polynomial + A dictionary of ``(ETuple, coeff)`` pairs. """ cdef dict subbed, new_e cdef ETuple exp, lm @@ -396,7 +396,7 @@ cdef dict subs_squares(dict eq_dict, dict known_sq): cdef dict remove_gcf(dict eq_dict, ETuple nonz): r""" - Returns a dictionary of ``(ETuple, coeff)`` pairs describing the + Return a dictionary of ``(ETuple, coeff)`` pairs describing the polynomial ``eq / GCF(eq)``. The input ``nonz`` is an ``ETuple`` indicating the positions of From 082445ff85eb96f6e43919089d131deced3941e7 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Wed, 7 Apr 2021 11:54:10 -0700 Subject: [PATCH 037/414] doctest tweaks in fusion_ring.py and f_matrix.py --- src/sage/combinat/root_system/f_matrix.py | 4 +--- src/sage/combinat/root_system/fusion_ring.py | 14 +++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 48872ce5480..795e65b23c0 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1489,9 +1489,7 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 - sage: f._triangular_elim(worker_pool=pool) # indirect doctest - Elimination epoch completed... 10 eqns remain in ideal basis - Elimination epoch completed... 0 eqns remain in ideal basis + sage: f._triangular_elim(worker_pool=pool,verbose=False) # indirect doctest sage: f.ideal_basis [] """ diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index c402fd6b775..09ca6d348bf 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -393,8 +393,8 @@ def test_braid_representation(self, max_strands=6): EXAMPLES:: - sage: F41 = FusionRing("F4",1) - sage: F41.test_braid_representation(max_strands=4) + sage: A21 = FusionRing("A2",1) + sage: A21.test_braid_representation(max_strands=4) True sage: B22 = FusionRing("B2",2) # long time sage: B22.test_braid_representation() # long time @@ -550,10 +550,10 @@ def fvars_field(self): Cyclotomic Field of order 40 and degree 16 sage: a2**4 2*a0 + 3*a2 - sage: comp_basis, sig = A13.get_braid_generators(a2,a2,3,verbose=False) - sage: A13.fvars_field() + sage: comp_basis, sig = A13.get_braid_generators(a2,a2,3,verbose=False) # long time (<3s) + sage: A13.fvars_field() # long time Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 - sage: a2.q_dimension().parent() + sage: a2.q_dimension().parent() # long time Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 sage: A13.field() Cyclotomic Field of order 40 and degree 16 @@ -855,8 +855,8 @@ def s_ijconj(self, elt_i, elt_j, base_coercion=True): True sage: G22 = FusionRing("G2",2) sage: G22.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) - sage: b = G22.basis() - sage: all(G22.s_ijconj(x,y) == G22.fmats.field()(G22.s_ij(x,y,base_coercion=False).conjugate()) for x in b for y in b) + sage: b = G22.basis() # long time + sage: all(G22.s_ijconj(x,y) == G22.fmats.field()(G22.s_ij(x,y,base_coercion=False).conjugate()) for x in b for y in b) # long time True """ ret = self.s_ij(elt_i, elt_j, base_coercion=False).conjugate() From 4ef0bdf13ec00042572ceb7886393a9c5e17e3d1 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 7 Apr 2021 16:45:43 -0400 Subject: [PATCH 038/414] shorter doctest in _get_explicit_solution --- src/sage/combinat/root_system/f_matrix.py | 34 ++++++++++------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 795e65b23c0..4b330d1a70e 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1840,27 +1840,24 @@ def _get_explicit_solution(self,eqns=None,verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",4)) # indirect doctest + sage: f = FMatrix(FusionRing("A1",3)) # indirect doctest sage: f.find_orthogonal_solution() # long time - Computing F-symbols for The Fusion Ring of Type A1 and level 4 with Integer Ring coefficients with 238 variables... - Set up 460 hex and orthogonality constraints... - Partitioned 460 equations into 41 components of size: - [24, 12, 12, 12, 12, 12, 12, 12, 12, 9, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1] - Elimination epoch completed... 63 eqns remain in ideal basis - Elimination epoch completed... 5 eqns remain in ideal basis + Set up 134 hex and orthogonality constraints... + Partitioned 134 equations into 17 components of size: + [12, 12, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1] + Elimination epoch completed... 10 eqns remain in ideal basis Elimination epoch completed... 0 eqns remain in ideal basis - Hex elim step solved for 173 / 238 variables - Set up 888 reduced pentagons... - Elimination epoch completed... 562 eqns remain in ideal basis - Elimination epoch completed... 209 eqns remain in ideal basis - Elimination epoch completed... 26 eqns remain in ideal basis - Elimination epoch completed... 8 eqns remain in ideal basis + Hex elim step solved for 51 / 71 variables + Set up 121 reduced pentagons... + Elimination epoch completed... 18 eqns remain in ideal basis + Elimination epoch completed... 5 eqns remain in ideal basis + Pent elim step solved for 64 / 71 variables + Partitioned 5 equations into 1 components of size: + [4] Elimination epoch completed... 0 eqns remain in ideal basis - Pent elim step solved for 225 / 238 variables - Partitioned 0 equations into 0 components of size: - [] - Partitioned 13 equations into 13 components of size: - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + Partitioned 6 equations into 6 components of size: + [1, 1, 1, 1, 1, 1] + Computing appropriate NumberField... """ if eqns is None: eqns = self.ideal_basis @@ -1939,7 +1936,6 @@ def _get_explicit_solution(self,eqns=None,verbose=True): self._fvars = {sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({},nvars),rhs),) - # self._backward_subs() _backward_subs(self) self._fvars = {sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items()} From 806ba0efc34705aeffc51edc98e2be266c426e7f Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 10 Apr 2021 18:11:45 +1000 Subject: [PATCH 039/414] Some formatting changes from initial reviewer pass. --- src/doc/en/reference/references/index.rst | 5 +- src/sage/combinat/root_system/f_matrix.py | 382 +++++++++--------- .../fast_parallel_fmats_methods.pxd | 1 + .../fast_parallel_fmats_methods.pyx | 110 ++--- .../fast_parallel_fusion_ring_braid_repn.pxd | 1 + .../fast_parallel_fusion_ring_braid_repn.pyx | 67 +-- src/sage/combinat/root_system/fusion_ring.py | 150 +++---- .../combinat/root_system/poly_tup_engine.pxd | 1 + .../combinat/root_system/poly_tup_engine.pyx | 133 +++--- 9 files changed, 444 insertions(+), 406 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 7aa2c0abb6f..0e4d35795e9 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1761,11 +1761,12 @@ REFERENCES: curves. preprint, 2005. .. [CHW2015] Shawn X.; Hong, Seung-Moon; Wang, Zhenghan Universal quantum computation - with weakly integral anyons. Quantum Inf. Process. 14 (2015), no. 8, 2687–2727. + with weakly integral anyons. Quantum Inf. Process. 14 (2015), + no. 8, 2687-2727. .. [CW2015] Cui, S. X. and Wang, Z. (2015). Universal quantum computation with metaplectic anyons. Journal of Mathematical Physics, 56(3), 032202. - doi:10.1063/1.4914941 + :doi:`10.1063/1.4914941` .. _ref-D: diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 4b330d1a70e..a14ed999c05 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1,4 +1,4 @@ -""" +r""" F-Matrix Factory for FusionRings """ # **************************************************************************** @@ -17,6 +17,7 @@ import cPickle as pickle except: import pickle + from copy import deepcopy from itertools import product, zip_longest from multiprocessing import cpu_count, Pool, set_start_method @@ -47,23 +48,24 @@ class FMatrix(): def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): - r"""Return an F-Matrix factory for a :class:`FusionRing`. + r""" + Return an F-Matrix factory for a :class:`FusionRing`. INPUT: - - ``FR`` -- a :class:`FusionRing`. + - ``FR`` -- a :class:`FusionRing` - ``fusion_label`` -- (optional) a string used to label basis elements - of the :class:`FusionRing` associated to ``self``. + of the :class:`FusionRing` associated to ``self`` - See :meth:`FusionRing.fusion_labels`. + See :meth:`FusionRing.fusion_labels` - ``var_prefix`` -- (optional) a string indicating the desired prefix - for variables denoting F-symbols to be solved. + for variables denoting F-symbols to be solved - ``inject_variables`` -- (default: ``False``) a boolean indicating whether to inject variables (:class:`FusionRing` basis element - labels and F-symbols) into the global namespace. + labels and F-symbols) into the global namespace The :class:`FusionRing` or Verlinde algebra is the Grothendieck ring of a modular tensor category [BaKi2001]_. @@ -84,23 +86,23 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab the multiplicity-free cases are given by the following table. - +------------------------+----------+ - | Cartan Type | `k` | - +========================+==========+ - | `A_1` | any | - +------------------------+----------+ - | `A_r, r\geq 2` | `\leq 2` | - +------------------------+----------+ - | `B_r, r\geq 2` | `\leq 2` | - +------------------------+----------+ - | `C_2` | `\leq 2` | - +------------------------+----------+ - | `C_r, r\geq 3` | `\leq 1` | - +------------------------+----------+ - | `D_r, r\geq 4` | `\leq 2` | - +------------------------+----------+ - | `G_2,F_4,E_r` | `\leq 2` | - +------------------------+----------+ + +------------------------+----------+ + | Cartan Type | `k` | + +========================+==========+ + | `A_1` | any | + +------------------------+----------+ + | `A_r, r\geq 2` | `\leq 2` | + +------------------------+----------+ + | `B_r, r\geq 2` | `\leq 2` | + +------------------------+----------+ + | `C_2` | `\leq 2` | + +------------------------+----------+ + | `C_r, r\geq 3` | `\leq 1` | + +------------------------+----------+ + | `D_r, r\geq 4` | `\leq 2` | + +------------------------+----------+ + | `G_2,F_4,E_r` | `\leq 2` | + +------------------------+----------+ Beyond this limitation, computation of the F-matrix can involve very large systems of equations. A @@ -120,7 +122,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab .. MATH:: - \text{Hom}(D,(A\otimes B)\otimes C) \to \text{Hom}(D,A\otimes(B\otimes C)) + \text{Hom}(D,(A\otimes B)\otimes C) + \to \text{Hom}(D,A\otimes(B\otimes C)) by a matrix `F^{ABC}_D`. This depends on a pair of additional simple objects `X` and `Y`. Indeed, we can @@ -138,8 +141,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab [RoStWa2009]_ and [CHW2015]_. The F-matrix is only determined up to a *gauge*. This - is a family of embeddings `C\to A\otimes B` for - simple objects `A,B,C` such that `\text{Hom}(C,A\otimes B)` + is a family of embeddings `C \to A\otimes B` for + simple objects `A,B,C` such that `\text{Hom}(C, A\otimes B)` is nonzero. Changing the gauge changes the F-matrix though not in a very essential way. By varying the gauge it is possible to make the F-matrices unitary, or it is possible @@ -148,7 +151,6 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab Due to the large number of equations we may fail to find a Groebner basis if there are too many variables. - EXAMPLES:: sage: I = FusionRing("E8",2,conjugate=True) @@ -158,16 +160,16 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients - We've exported two sets of variables to the global namespace. + We have injected two sets of variables to the global namespace. We created three variables ``i0, p, s`` to represent the - primary fields (simple elements) of the FusionRing. Creating - the FMatrix factory also created variables ``fx1,fx2, ... , fx14`` - in order to solve the hexagon and pentagon equations describing - the F-matrix. Since we called :class:`FMatrix` with the parameter - ``inject_variables=True``, these have been exported into the global - namespace. This is not necessary for the code to work but if you want - to run the code experimentally you may want access to these - variables. + primary fields (simple elements) of the :class:`FusionRing`. Creating + the :class:`FMatrix` factory also created variables + ``fx1, fx2, ..., fx14`` in order to solve the hexagon and pentagon + equations describing the F-matrix. Since we called :class:`FMatrix` + with the parameter ``inject_variables=True``, these have been injected + into the global namespace. This is not necessary for the code to work + but if you want to run the code experimentally you may want access + to these variables. EXAMPLES:: @@ -186,7 +188,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab for these are. In this example with `A=B=C=D=s` both `X` and `Y` are allowed to be `i_0` or `s`. - EXAMPLES:: + :: sage: f.f_from(s,s,s,s), f.f_to(s,s,s,s) ([i0, p], [i0, p]) @@ -195,15 +197,15 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab `X` and `Y` when `A=B=C=D=s` are `i_0` and `p`. The F-matrix is computed by solving the so-called - pentagon and hexagon equations. The *pentagon - equations* reflect the Mac Lane pentagon axiom in the - definition of a monoidal category. The hexagon relations + pentagon and hexagon equations. The *pentagon equations* + reflect the Mac Lane pentagon axiom in the definition + of a monoidal category. The hexagon relations reflect the axioms of a *braided monoidal category*, which are constraints on both the F-matrix and on the R-matrix. Optionally, orthogonality constraints may be imposed to obtain an orthogonal F-matrix. - EXAMPLES:: + :: sage: f.get_defining_equations("pentagons")[1:3] [fx9*fx12 - fx2*fx13, fx3*fx8 - fx4*fx9] @@ -221,12 +223,12 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab that should be kept in mind. :meth:`find_cyclotomic_solution` currently works only with - smaller examples. For example the :class:`FusionRing` for `G_2` at - level 2 is too large. When it is available, this method + smaller examples. For example the :class:`FusionRing` for `G_2` + at level 2 is too large. When it is available, this method produces an F-matrix whose entries are in the same cyclotomic field as the underlying :class:`FusionRing`. - EXAMPLES:: + :: sage: f.find_cyclotomic_solution() Setting up hexagons and pentagons... @@ -238,9 +240,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab Done! We now have access to the values of the F-matrix using - the methods :meth:`fmatrix` and :meth:`fmat`. - - EXAMPLES:: + the methods :meth:`fmatrix` and :meth:`fmat`:: sage: f.fmatrix(s,s,s,s) [(-1/2*zeta128^48 + 1/2*zeta128^16) 1] @@ -260,11 +260,11 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab containing the F-matrix. The field containing the F-matrix is available through :meth:`field`. - EXAMPLES:: + :: sage: f = FMatrix(FusionRing("B3",2)) - sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # long time (~100 s) - sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # long time + sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # not tested (~100 s) + sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # not tested True sage: f = FMatrix(FusionRing("G2",2)) @@ -281,17 +281,17 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab n_vars = self.findcases() self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) if inject_variables: - print ("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) + print("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) self._poly_ring.inject_variables(get_main_globals()) self._var_to_sextuple, self._fvars = self.findcases(output=True) - self._var_to_idx = {var : idx for idx, var in enumerate(self._poly_ring.gens())} - self._idx_to_sextuple = {i : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(self._poly_ring.ngens())} + self._var_to_idx = {var: idx for idx, var in enumerate(self._poly_ring.gens())} + self._idx_to_sextuple = {i: self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(self._poly_ring.ngens())} self._singles = self.singletons() #Base field attributes self._field = self._FR.field() - r = self._field.defining_polynomial().roots(ring=QQbar,multiplicities=False)[0] - self._qqbar_embedding = self._field.hom([r],QQbar) + r = self._field.defining_polynomial().roots(ring=QQbar, multiplicities=False)[0] + self._qqbar_embedding = self._field.hom([r], QQbar) self._non_cyc_roots = list() #Useful solver state attributes @@ -318,7 +318,7 @@ def __repr__(self): return "F-Matrix factory for %s"%self._FR def clear_equations(self): - """ + r""" Clear the list of equations to be solved. EXAMPLES:: @@ -334,7 +334,7 @@ def clear_equations(self): self.ideal_basis = list() def clear_vars(self): - """ + r""" Reset the F-symbols. EXAMPLES:: @@ -355,7 +355,7 @@ def clear_vars(self): self._solved = set() def _reset_solver_state(self): - """ + r""" Reset solver state and clear relevant cache. Used to ensure state variables are the same for each orthogonal solver run. @@ -405,7 +405,7 @@ def _reset_solver_state(self): self._FR.s_ij.clear_cache() def _update_poly_ring_base_field(self,field): - """ + r""" Change base field of ``PolynomialRing`` and the corresponding index attributes @@ -423,13 +423,13 @@ def _update_poly_ring_base_field(self,field): new_poly_ring = self._poly_ring.change_ring(field) nvars = self._poly_ring.ngens() #Do some appropriate conversions - self._var_to_idx = {new_poly_ring.gen(i) : i for i in range(nvars)} - self._var_to_sextuple = {new_poly_ring.gen(i) : self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars)} + self._var_to_idx = {new_poly_ring.gen(i): i for i in range(nvars)} + self._var_to_sextuple = {new_poly_ring.gen(i): self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars)} self._poly_ring = new_poly_ring def fmat(self, a, b, c, d, x, y, data=True): - """ - Return the F-Matrix coefficient `(F^{a,b,c}_d)_{x,y}` + r""" + Return the F-Matrix coefficient `(F^{a,b,c}_d)_{x,y}`. EXAMPLES:: @@ -484,7 +484,7 @@ def fmat(self, a, b, c, d, x, y, data=True): return (a,b,c,d,x,y) def fmatrix(self,a,b,c,d): - """ + r""" Return the F-Matrix `F^{a,b,c}_d`. INPUT: @@ -508,7 +508,6 @@ def fmatrix(self,a,b,c,d): [ (1/2*zeta32^12 - 1/2*zeta32^4) (-1/2*zeta32^12 + 1/2*zeta32^4)] [ (1/2*zeta32^12 - 1/2*zeta32^4) (1/2*zeta32^12 - 1/2*zeta32^4)] """ - X = self.f_from(a,b,c,d) Y = self.f_to(a,b,c,d) return matrix([[self.fmat(a,b,c,d,x,y) for y in Y] for x in X]) @@ -546,7 +545,7 @@ def field(self): Cyclotomic Field of order 60 and degree 16 sage: f.find_orthogonal_solution(verbose=False) sage: f.field() - Number Field in a with defining polynomial y^32 - 6*y^30 - 7*y^28 + 62*y^26 - 52*y^24 - 308*y^22 + 831*y^20 + 7496*y^18 + 18003*y^16 - 2252*y^14 + 42259*y^12 - 65036*y^10 + 29368*y^8 - 3894*y^6 + 377*y^4 - 22*y^2 + 1 + Number Field in a with defining polynomial y^32 - ... - 22*y^2 + 1 sage: phi = f.get_qqbar_embedding() sage: [phi(r).n() for r in f.get_non_cyclotomic_roots()] [-0.786151377757423 - 8.92806368517581e-31*I] @@ -573,7 +572,7 @@ def FR(self): return self._FR def findcases(self,output=False): - """ + r""" Return unknown F-matrix entries. If run with ``output=True``, @@ -587,16 +586,15 @@ def findcases(self,output=False): 5 sage: f.findcases(output=True) ({fx4: (t, t, t, t, t, t), - fx3: (t, t, t, t, t, i0), - fx2: (t, t, t, t, i0, t), - fx1: (t, t, t, t, i0, i0), - fx0: (t, t, t, i0, t, t)}, - {(t, t, t, i0, t, t): fx0, - (t, t, t, t, i0, i0): fx1, - (t, t, t, t, i0, t): fx2, - (t, t, t, t, t, i0): fx3, - (t, t, t, t, t, t): fx4}) - + fx3: (t, t, t, t, t, i0), + fx2: (t, t, t, t, i0, t), + fx1: (t, t, t, t, i0, i0), + fx0: (t, t, t, i0, t, t)}, + {(t, t, t, i0, t, t): fx0, + (t, t, t, t, i0, i0): fx1, + (t, t, t, t, i0, t): fx2, + (t, t, t, t, t, i0): fx3, + (t, t, t, t, t, t): fx4}) """ i = 0 if output: @@ -630,8 +628,8 @@ def singletons(self): True """ ret = [] - for (a, b, c, d) in list(product(self._FR.basis(), repeat=4)): - (ff,ft) = (self.f_from(a,b,c,d),self.f_to(a,b,c,d)) + for (a, b, c, d) in product(self._FR.basis(), repeat=4): + (ff,ft) = (self.f_from(a,b,c,d), self.f_to(a,b,c,d)) if len(ff) == 1 and len(ft) == 1: v = self._fvars.get((a,b,c,d,ff[0],ft[0]), None) if v in self._poly_ring.gens(): @@ -641,11 +639,11 @@ def singletons(self): def f_from(self,a,b,c,d): r""" Return the possible `x` such that there are morphisms - `d\to x\otimes c\to (a\otimes b)\otimes c`. + `d \to x \otimes c \to (a\otimes b)\otimes c`. INPUT: - - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing`. + - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing` EXAMPLES:: @@ -659,8 +657,8 @@ def f_from(self,a,b,c,d): sage: f.f_to(a1,a1,a2,a2) [a1, a3] """ - - return [x for x in self._FR.basis() if self._FR.Nk_ij(a,b,x) != 0 and self._FR.Nk_ij(x,c,d) != 0] + return [x for x in self._FR.basis() + if self._FR.Nk_ij(a,b,x) != 0 and self._FR.Nk_ij(x,c,d) != 0] def f_to(self,a,b,c,d): r""" @@ -669,7 +667,7 @@ def f_to(self,a,b,c,d): INPUT: - - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing`. + - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing` EXAMPLES:: @@ -684,9 +682,9 @@ def f_to(self,a,b,c,d): [b1, b3, b5] sage: B.f_to(b2,b4,b2,b4) [b1, b3, b5] - """ - return [y for y in self._FR.basis() if self._FR.Nk_ij(b,c,y) != 0 and self._FR.Nk_ij(a,y,d) != 0] + return [y for y in self._FR.basis() + if self._FR.Nk_ij(b,c,y) != 0 and self._FR.Nk_ij(a,y,d) != 0] #################### ### Data getters ### @@ -723,7 +721,8 @@ def get_poly_ring(self): sage: f = FMatrix(FusionRing("B6",1)) sage: f.get_poly_ring() - Multivariate Polynomial Ring in fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 over Cyclotomic Field of order 96 and degree 32 + Multivariate Polynomial Ring in fx0, ..., fx13 over + Cyclotomic Field of order 96 and degree 32 """ return self._poly_ring @@ -767,8 +766,7 @@ def get_qqbar_embedding(self): r""" Return an embedding from the base field containing F-symbols (the associated :class:`FusionRing`'s :class:`CyclotomicField`, a - :class:`NumberField`, or ``QQbar``) - into ``QQbar``. + :class:`NumberField`, or ``QQbar``) into ``QQbar``. This embedding is useful for getting a better sense for the F-symbols, particularly when they are computed as elements of a @@ -825,7 +823,7 @@ def get_coerce_map_from_fr_cyclotomic_field(self): we have ``self.field() == self.FR().field()`` and this method returns the identity map on ``self.field()``. - EXAMPLES:: + :: sage: f = FMatrix(FusionRing("A2",1)) sage: f.find_orthogonal_solution(verbose=False) @@ -862,7 +860,7 @@ def get_fvars_in_alg_field(self): Defining fx0, fx1, fx2, fx3, fx4 sage: f.find_orthogonal_solution(verbose=False) sage: f.field() - Number Field in a with defining polynomial y^32 - 6*y^30 - 7*y^28 + 62*y^26 - 52*y^24 - 308*y^22 + 831*y^20 + 7496*y^18 + 18003*y^16 - 2252*y^14 + 42259*y^12 - 65036*y^10 + 29368*y^8 - 3894*y^6 + 377*y^4 - 22*y^2 + 1 + Number Field in a with defining polynomial y^32 - ... - 22*y^2 + 1 sage: f.get_fvars_in_alg_field() {(g1, g1, g1, g0, g1, g1): 1, (g1, g1, g1, g1, g0, g0): 0.61803399? + 0.?e-8*I, @@ -870,7 +868,7 @@ def get_fvars_in_alg_field(self): (g1, g1, g1, g1, g1, g0): -0.7861514? + 0.?e-8*I, (g1, g1, g1, g1, g1, g1): -0.61803399? + 0.?e-8*I} """ - return {sextuple : self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items()} + return {sextuple: self._qqbar_embedding(fvar) for sextuple, fvar in self._fvars.items()} def get_radical_expression(self): """ @@ -885,7 +883,7 @@ def get_radical_expression(self): sage: radical_fvars[g1, g1, g1, g1, g1, g0] # long time -sqrt(1/2*sqrt(5) - 1/2) """ - return {sextuple : val.radical_expression() for sextuple, val in self.get_fvars_in_alg_field().items()} + return {sextuple: val.radical_expression() for sextuple, val in self.get_fvars_in_alg_field().items()} ####################### ### Private helpers ### @@ -893,8 +891,8 @@ def get_radical_expression(self): def _get_known_vals(self): r""" - Construct a dictionary of ``idx``, ``known_val`` pairs used for substituting - into remaining equations. + Construct a dictionary of ``idx``, ``known_val`` pairs used for + substituting into remaining equations. EXAMPLES:: @@ -905,7 +903,7 @@ def _get_known_vals(self): sage: len(f._get_known_vals()) == f._poly_ring.ngens() True """ - return {var_idx : self._fvars[self._idx_to_sextuple[var_idx]] for var_idx in self._solved} + return {var_idx: self._fvars[self._idx_to_sextuple[var_idx]] for var_idx in self._solved} def _get_known_sq(self,eqns=None): r""" @@ -952,14 +950,14 @@ def _get_known_nonz(self): .. NOTE:: MUST be called after ``self._ks = _get_known_sq()``. - This method is called by the constructor of ``self``. EXAMPLES:: sage: f = FMatrix(FusionRing("D5",1)) # indirect doctest sage: f._nnz - (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) + (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, + 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) """ nonz = {self._var_to_idx[var] : 100 for var in self._singles} for idx in self._ks: @@ -989,30 +987,30 @@ def get_fvars_by_size(self,n,indices=False): INPUT: - - `n` -- a positive integer + - `n` -- a positive integer - - ``indices`` -- (default: ``False``) a boolean. + - ``indices`` -- (default: ``False``) a boolean. - If ``indices`` is ``False`` (default), - this method returns a set of sextuples `(a,b,c,d,x,y)` identifying - the corresponding F-symbol. Each sextuple is a key in the - dictionary returned by :meth:`get_fvars`. + If ``indices`` is ``False`` (default), + this method returns a set of sextuples `(a,b,c,d,x,y)` identifying + the corresponding F-symbol. Each sextuple is a key in the + dictionary returned by :meth:`get_fvars`. - Otherwise the method returns a list of integer indices that - internally identify the F-symbols. The ``indices=True`` option is - meant for internal use. + Otherwise the method returns a list of integer indices that + internally identify the F-symbols. The ``indices=True`` option is + meant for internal use. EXAMPLES:: sage: f = FMatrix(FusionRing("A2",2), inject_variables=True) creating variables fx1..fx287 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26, fx27, fx28, fx29, fx30, fx31, fx32, fx33, fx34, fx35, fx36, fx37, fx38, fx39, fx40, fx41, fx42, fx43, fx44, fx45, fx46, fx47, fx48, fx49, fx50, fx51, fx52, fx53, fx54, fx55, fx56, fx57, fx58, fx59, fx60, fx61, fx62, fx63, fx64, fx65, fx66, fx67, fx68, fx69, fx70, fx71, fx72, fx73, fx74, fx75, fx76, fx77, fx78, fx79, fx80, fx81, fx82, fx83, fx84, fx85, fx86, fx87, fx88, fx89, fx90, fx91, fx92, fx93, fx94, fx95, fx96, fx97, fx98, fx99, fx100, fx101, fx102, fx103, fx104, fx105, fx106, fx107, fx108, fx109, fx110, fx111, fx112, fx113, fx114, fx115, fx116, fx117, fx118, fx119, fx120, fx121, fx122, fx123, fx124, fx125, fx126, fx127, fx128, fx129, fx130, fx131, fx132, fx133, fx134, fx135, fx136, fx137, fx138, fx139, fx140, fx141, fx142, fx143, fx144, fx145, fx146, fx147, fx148, fx149, fx150, fx151, fx152, fx153, fx154, fx155, fx156, fx157, fx158, fx159, fx160, fx161, fx162, fx163, fx164, fx165, fx166, fx167, fx168, fx169, fx170, fx171, fx172, fx173, fx174, fx175, fx176, fx177, fx178, fx179, fx180, fx181, fx182, fx183, fx184, fx185, fx186, fx187, fx188, fx189, fx190, fx191, fx192, fx193, fx194, fx195, fx196, fx197, fx198, fx199, fx200, fx201, fx202, fx203, fx204, fx205, fx206, fx207, fx208, fx209, fx210, fx211, fx212, fx213, fx214, fx215, fx216, fx217, fx218, fx219, fx220, fx221, fx222, fx223, fx224, fx225, fx226, fx227, fx228, fx229, fx230, fx231, fx232, fx233, fx234, fx235, fx236, fx237, fx238, fx239, fx240, fx241, fx242, fx243, fx244, fx245, fx246, fx247, fx248, fx249, fx250, fx251, fx252, fx253, fx254, fx255, fx256, fx257, fx258, fx259, fx260, fx261, fx262, fx263, fx264, fx265, fx266, fx267, fx268, fx269, fx270, fx271, fx272, fx273, fx274, fx275, fx276, fx277, fx278, fx279, fx280, fx281, fx282, fx283, fx284, fx285, fx286 + Defining fx0, ..., fx286 sage: f.largest_fmat_size() 2 sage: f.get_fvars_by_size(2) {(f2, f2, f2, f4, f1, f1), (f2, f2, f2, f4, f1, f5), - ... + ... (f4, f4, f4, f4, f4, f0), (f4, f4, f4, f4, f4, f4)} """ @@ -1042,7 +1040,7 @@ def save_fvars(self, filename): INPUT: - ``filename`` -- a string specifying the name of the pickle file - to be used. + to be used The current directory is used unless an absolute path to a file in a different directory is provided. @@ -1122,20 +1120,20 @@ def load_fvars(self, filename): self._field = self._qqbar_embedding.domain() def get_fr_str(self): - """ - Auto-generate an identifying key for saving results + r""" + Auto-generate an identifying key for saving results. EXAMPLES:: - sage: f = FMatrix(FusionRing("B3",1)) - sage: f.get_fr_str() - 'B31' + sage: f = FMatrix(FusionRing("B3",1)) + sage: f.get_fr_str() + 'B31' """ ct = self._FR.cartan_type() return ct.letter + str(ct.n) + str(self._FR.fusion_level()) - def _checkpoint(self,do_chkpt,status,verbose=True): - """ + def _checkpoint(self, do_chkpt, status, verbose=True): + r""" Pickle current solver state. EXAMPLES:: @@ -1276,8 +1274,8 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t INPUT: - -``mapper`` -- string specifying the name of a function defined in - the ``fast_parallel_fmats_methods`` module. + -``mapper`` -- string specifying the name of a function defined in + the ``fast_parallel_fmats_methods`` module .. NOTE:: @@ -1338,7 +1336,7 @@ def get_orthogonality_constraints(self,output=True): INPUT: - - ``output`` -- a boolean. + - ``output`` -- a boolean OUTPUT: @@ -1352,7 +1350,7 @@ def get_orthogonality_constraints(self,output=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("B4",1)) + sage: f = FMatrix(FusionRing("B4", 1)) sage: f.get_orthogonality_constraints() [fx0^2 - 1, fx1^2 - 1, @@ -1369,7 +1367,7 @@ def get_orthogonality_constraints(self,output=True): fx10*fx11 + fx12*fx13, fx11^2 + fx13^2 - 1] """ - eqns = list() + eqns = [] for tup in product(self._FR.basis(), repeat=4): mat = self.fmatrix(*tup) eqns.extend((mat.T * mat - matrix.identity(mat.nrows())).coefficients()) @@ -1384,29 +1382,24 @@ def get_defining_equations(self,option,worker_pool=None,output=True): INPUT: - - ``option`` -- a string determining equations to be set up. + - ``option`` -- a string determining equations to be set up: - Use ``option='hexagons'`` to get equations imposed on the F-matrix by - the hexagon relations in the definition of a braided category. + * ``'hexagons'`` - get equations imposed on the F-matrix by + the hexagon relations in the definition of a braided category - Use ``option='pentagons'`` to get equations imposed on the F-matrix by - the pentagon relations in the definition of a monoidal category. + * ``'pentagons'`` - get equations imposed on the F-matrix by + the pentagon relations in the definition of a monoidal category - ``worker_pool`` -- (default: ``None``) a ``Pool`` object of the - Python ``multiprocessing`` module. - - If a ``worker_pool`` object is passed, we distribute the work - amongst processes in the pool. + Python ``multiprocessing`` module - ``output`` -- (default: ``True``) a boolean indicating whether - results should be returned. - - If ``output=True``, equations are returned as polynomial objects. + results should be returned, where the equations will be polynomials. - Otherwise, the constraints are appended to ``self.ideal_basis``. - They are stored in the internal tuple representation. The - ``output=False`` option is meant mostly for internal use by the - F-matrix solver. + Otherwise, the constraints are appended to ``self.ideal_basis``. + They are stored in the internal tuple representation. The + ``output=False`` option is meant mostly for internal use by the + F-matrix solver. EXAMPLES:: @@ -1471,7 +1464,7 @@ def _tup_to_fpoly(self,eq_tup): return _tup_to_poly(eq_tup,parent=self._poly_ring) def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): - """ + r""" Update reduction parameters that are solver state attributes. EXAMPLES:: @@ -1489,7 +1482,7 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 - sage: f._triangular_elim(worker_pool=pool,verbose=False) # indirect doctest + sage: f._triangular_elim(worker_pool=pool,verbose=False) # indirect doctest sage: f.ideal_basis [] """ @@ -1505,14 +1498,14 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): - """ + r""" Perform triangular elimination of linear terms in two-term equations until no such terms exist. .. NOTE:: - For optimal usage of TRIANGULAR elimination, pass in a - SORTED list of equations. + For optimal usage of triangular elimination, pass in a + *sorted* list of equations. EXAMPLES:: @@ -1536,7 +1529,8 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): while True: linear_terms_exist = _solve_for_linear_terms(self,eqns) - if not linear_terms_exist: break + if not linear_terms_exist: + break _backward_subs(self) #Compute new reduction params, send to child processes if any, and update eqns @@ -1557,7 +1551,7 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): ##################### def equations_graph(self,eqns=None): - """ + r""" Construct a graph corresponding to the given equations. Every node corresponds to a variable and nodes are connected when @@ -1565,7 +1559,7 @@ def equations_graph(self,eqns=None): INPUT: - - ``eqns`` -- a list of polynomials. + - ``eqns`` -- a list of polynomials Each polynomial is either an object in the ring returned by :meth:`get_poly_ring` or it is a tuple of pairs representing @@ -1626,7 +1620,7 @@ def equations_graph(self,eqns=None): return G def _partition_eqns(self,eqns=None,verbose=True): - """ + r""" Partition equations corresponding to edges in a disconnected graph. OUTPUT: @@ -1662,7 +1656,7 @@ def _partition_eqns(self,eqns=None,verbose=True): if eqns is None: eqns = self.ideal_basis graph = self.equations_graph(eqns) - partition = {tuple(c) : [] for c in graph.connected_components()} + partition = {tuple(c): [] for c in graph.connected_components()} for eq_tup in eqns: partition[tuple(graph.connected_component_containing_vertex(variables(eq_tup)[0]))].append(eq_tup) if verbose: @@ -1671,18 +1665,18 @@ def _partition_eqns(self,eqns=None,verbose=True): return partition def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): - """ - Compute a Groebner basis for a list of equations partitioned according - to their corresponding graph. + r""" + Compute a Groebner basis for a list of equations partitioned + according to their corresponding graph. .. NOTE:: - If the graph has more than 50 components, this method computes the - Groebner basis in parallel when a ``worker_pool`` is provided. + If the graph has more than 50 components, this method computes the + Groebner basis in parallel when a ``worker_pool`` is provided. - This method will refuse to find a Groebner basis for a component - of size larger than 60, since such a calculation does not seem to - terminate. + This method will refuse to find a Groebner basis for a component + of size larger than 60, since such a calculation does not seem to + terminate. EXAMPLES:: @@ -1734,9 +1728,9 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose return ret def _get_component_variety(self,var,eqns): - """ - Translate equations in each connected component to smaller polynomial rings - so we can call built-in variety method. + r""" + Translate equations in each connected component to smaller polynomial + rings so we can call built-in variety method. INPUT: @@ -1755,7 +1749,8 @@ def _get_component_variety(self,var,eqns): sage: f.get_defining_equations('hexagons',worker_pool=Pool(),output=False) # long time sage: partition = f._partition_eqns() # long time Partitioned 327 equations into 35 components of size: - [27, 27, 27, 24, 24, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 9, 9, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1] + [27, 27, 27, 24, 24, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 9, 9, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1] sage: c = (216, 292, 319) sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: eqns = partition[c] + [poly_to_tup(f._poly_ring.gen(216)-1)] # long time @@ -1763,25 +1758,25 @@ def _get_component_variety(self,var,eqns): [{216: -1, 292: -1, 319: 1}] """ #Define smaller poly ring in component vars - R = PolynomialRing(self._FR.field(),len(var),'a',order='lex') + R = PolynomialRing(self._FR.field(), len(var), 'a', order='lex') #Zip tuples into R and compute Groebner basis - idx_map = {old : new for new, old in enumerate(sorted(var))} + idx_map = {old: new for new, old in enumerate(sorted(var))} nvars = len(var) eqns = [_unflatten_coeffs(self._field,eq_tup) for eq_tup in eqns] polys = [_tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R) for eq_tup in eqns] var_in_R = Ideal(sorted(polys)).variety(ring=AA) #Change back to fmats poly ring and append to temp_eqns - inv_idx_map = {v : k for k, v in idx_map.items()} - return [{inv_idx_map[i] : value for i, (key, value) in enumerate(sorted(soln.items()))} for soln in var_in_R] + inv_idx_map = {v: k for k, v in idx_map.items()} + return [{inv_idx_map[i]: value for i, (key, value) in enumerate(sorted(soln.items()))} for soln in var_in_R] ####################### ### Solution method ### ####################### def attempt_number_field_computation(self): - """ + r""" Based on the ``CartanType`` of ``self`` and data known on March 17, 2021, determine whether to attempt to find a :class:`NumberField` containing all the F-symbols. @@ -1790,8 +1785,8 @@ def attempt_number_field_computation(self): to determine a field containing all F-symbols. See :meth:`field` and :meth:`get_non_cyclotomic_roots`. - For certain :class:`FusionRing`'s, the number field computation does - not terminate in reasonable time. + For certain :class:`FusionRing `, the number field + computation does not terminate in reasonable time. In these cases, we report F-symbols as elements of the :class:`AlgebraicField` ``QQbar``. @@ -1832,11 +1827,11 @@ def attempt_number_field_computation(self): return True def _get_explicit_solution(self,eqns=None,verbose=True): - """ + r""" When this method is called, the solution is already found in terms of Groeber basis. A few degrees of freedom remain. - By specializing the free variables and back substituting, a solution in - the base field is now obtained. + By specializing the free variables and back substituting, a + solution in the base field is now obtained. EXAMPLES:: @@ -1943,7 +1938,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() - def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="",use_mp=True,verbose=True): + def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start="", use_mp=True, verbose=True): r""" Solve the the hexagon and pentagon relations, along with orthogonality constraints, to evaluate an orthogonal F-matrix. @@ -2037,13 +2032,15 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" to ``self``. See :meth:`attempt_number_field_computation` for details. """ self._reset_solver_state() - if self._poly_ring.ngens() == 0: return + if self._poly_ring.ngens() == 0: + return #Resume computation from checkpoint if warm_start: self._restore_state(warm_start) #Loading from a pickle with solved F-symbols - if self._chkpt_status > 5: return + if self._chkpt_status > 5: + return #Set multiprocessing parameters. Context can only be set once, so we try to set it try: @@ -2131,13 +2128,13 @@ def find_orthogonal_solution(self,checkpoint=False,save_results="",warm_start="" ######################### def _fix_gauge(self, algorithm=""): - """ + r""" Fix the gauge by forcing F-symbols not already fixed to equal 1. .. NOTE:: - This method should be used AFTER adding hexagon and pentagon - equations to ``self.ideal_basis``. + This method should be used *after* adding hexagon and pentagon + equations to ``self.ideal_basis``. EXAMPLES:: @@ -2175,7 +2172,7 @@ def _substitute_degree_one(self, eqns=None): sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + Defining fx0, ..., fx26 sage: f.ideal_basis = [fx0 - 8, fx4**2 - 3, fx4 + fx10 + 3, fx4 + fx9] sage: _, _ = f._substitute_degree_one() sage: f._fvars[f._var_to_sextuple[fx0]] @@ -2204,14 +2201,14 @@ def _substitute_degree_one(self, eqns=None): return new_knowns, useless def _update_equations(self): - """ + r""" Perform backward substitution on equations in ``self.ideal_basis``. EXAMPLES:: sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + Defining fx0, ..., fx26 sage: f.ideal_basis = [fx0 - 8, fx4 + fx9, fx4**2 + fx3 - fx9**2] sage: _, _ = f._substitute_degree_one() sage: f._update_equations() @@ -2222,8 +2219,8 @@ def _update_equations(self): self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) self.ideal_basis.discard(0) - def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, output=False): - """ + def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, output=False): + r""" Solve the the hexagon and pentagon relations to evaluate the F-matrix. This method (omitting the orthogonality constraints) produces @@ -2235,10 +2232,10 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o INPUT: - ``equations`` -- (optional) a set of equations to be - solved. Defaults to the hexagon and pentagon equations. - - ``algorithm`` -- (optional). Algorithm to compute Groebner Basis. - - ``output`` -- (optional, default False). Output a dictionary of - F-matrix values. This may be useful to see but may be omitted + solved; defaults to the hexagon and pentagon equations + - ``algorithm`` -- (optional) algorithm to compute Groebner Basis + - ``output`` -- (default: ``False``) output a dictionary of + F-matrix values; this may be useful to see but may be omitted since this information will be available afterwards via the :meth:`fmatrix` and :meth:`fmat` methods. @@ -2275,10 +2272,10 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o [] sage: f.get_defining_equations("pentagons") [] - """ self._reset_solver_state() - if self._poly_ring.ngens() == 0: return + if self._poly_ring.ngens() == 0: + return if equations is None: if verbose: @@ -2303,7 +2300,7 @@ def find_cyclotomic_solution(self, equations=None, algorithm='', verbose=True, o ##################### def certify_pentagons(self,use_mp=True,verbose=False): - """ + r""" Obtain a certificate of satisfaction for the pentagon equations, up to floating-point error. @@ -2341,7 +2338,7 @@ def certify_pentagons(self,use_mp=True,verbose=False): Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients """ fvars_copy = deepcopy(self._fvars) - self._fvars = {sextuple : float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} + self._fvars = {sextuple: float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} if use_mp: pool = Pool(processes=cpu_count()) else: @@ -2359,7 +2356,7 @@ def certify_pentagons(self,use_mp=True,verbose=False): return pe def fmats_are_orthogonal(self): - """ + r""" Verify that all F-matrices are orthogonal. This method should always return ``True`` when called after running @@ -2379,7 +2376,7 @@ def fmats_are_orthogonal(self): return all(is_orthog) def fvars_are_real(self): - """ + r""" Test whether all F-symbols are real. EXAMPLES:: @@ -2396,3 +2393,4 @@ def fvars_are_real(self): print("The F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) return False return True + diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index 2092478205b..861504a79a3 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -6,3 +6,4 @@ cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) cpdef executor(params) cpdef collect_eqns(proc) + diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index c2afc4b240c..a66b1d0da97 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -1,5 +1,5 @@ """ -Fast FMatrix methods +Fast F-Matrix methods """ # **************************************************************************** # Copyright (C) 2021 Guillermo Aboumrad @@ -31,8 +31,8 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing ### Fast class methods ### ########################## -cpdef _solve_for_linear_terms(factory,eqns=None): - """ +cpdef _solve_for_linear_terms(factory, eqns=None): + r""" Solve for a linear term occurring in a two-term equation, and for variables appearing in univariate single-term equations. @@ -40,7 +40,7 @@ cpdef _solve_for_linear_terms(factory,eqns=None): sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + Defining fx0, ..., fx26 sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] @@ -60,11 +60,12 @@ cpdef _solve_for_linear_terms(factory,eqns=None): fx4 """ if eqns is None: - eqns = factory.ideal_basis + eqns = factory.ideal_basis - linear_terms_exist = False + cdef bint linear_terms_exist = False + cdef tuple eq_tup for eq_tup in eqns: - #Only unflatten relevant polynomials + # Only unflatten relevant polynomials if len(eq_tup) > 2: continue eq_tup = _unflatten_coeffs(factory._field, eq_tup) @@ -89,7 +90,7 @@ cpdef _solve_for_linear_terms(factory,eqns=None): return linear_terms_exist cpdef _backward_subs(factory): - """ + r""" Perform backward substitution on ``self.ideal_basis``, traversing variables in reverse lexicographical order. @@ -97,7 +98,7 @@ cpdef _backward_subs(factory): sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + Defining fx0, ..., fx26 sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] @@ -134,10 +135,11 @@ cpdef _backward_subs(factory): for var in reversed(factory._poly_ring.gens()): sextuple = factory._var_to_sextuple[var] rhs = factory._fvars[sextuple] - d = {var_idx : factory._fvars[factory._idx_to_sextuple[var_idx]] for var_idx in variables(rhs) if var_idx in factory._solved} + d = {var_idx: factory._fvars[factory._idx_to_sextuple[var_idx]] + for var_idx in variables(rhs) if var_idx in factory._solved} if d: kp = compute_known_powers(get_variables_degrees([rhs]), d, one) - factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one),factory._ks).items()) + factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), factory._ks).items()) ###################################### ### Fast fusion coefficients cache ### @@ -175,7 +177,7 @@ cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): Cython version of fmat class method. Using cdef for fastest dispatch """ if _Nk_ij(a,b,x) == 0 or _Nk_ij(x,c,d) == 0 or _Nk_ij(b,c,y) == 0 or _Nk_ij(a,y,d) == 0: - return 0 + return 0 #Some known F-symbols if a == id_anyon: if x == b and y == d: @@ -196,15 +198,16 @@ cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): """ - Given an FMatrix factory and a sextuple, return a hexagon equation as a polynomial object + Given an FMatrix factory and a sextuple, return a hexagon equation + as a polynomial object. """ a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = r_matrix(a,c,e,base_coercion=False)*_fmat(fvars,Nk_ij,id_anyon,a,c,b,d,e,g)*r_matrix(b,c,g,base_coercion=False) + lhs = r_matrix(a,c,e,base_coercion=False) * _fmat(fvars,Nk_ij,id_anyon,a,c,b,d,e,g) * r_matrix(b,c,g,base_coercion=False) rhs = 0 for f in basis: - rhs += _fmat(fvars,Nk_ij,id_anyon,c,a,b,d,e,f)*r_matrix(f,c,d,base_coercion=False)*_fmat(fvars,Nk_ij,id_anyon,a,b,c,d,f,g) + rhs += _fmat(fvars,Nk_ij,id_anyon,c,a,b,d,e,f) * r_matrix(f,c,d,base_coercion=False) * _fmat(fvars,Nk_ij,id_anyon,a,b,c,d,f,g) return lhs-rhs @cython.wraparound(False) @@ -212,11 +215,11 @@ cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): @cython.cdivision(True) cdef get_reduced_hexagons(factory, tuple mp_params): """ - Set up and reduce the hexagon equations corresponding to this worker + Set up and reduce the hexagon equations corresponding to this worker. """ #Set up multiprocessing parameters global worker_results - cdef child_id, n_proc + cdef int child_id, n_proc cdef unsigned long i child_id, n_proc = mp_params cdef tuple sextuple, red @@ -232,7 +235,7 @@ cdef get_reduced_hexagons(factory, tuple mp_params): cdef dict _ks = factory._ks #Computation loop - for i, sextuple in enumerate(product(basis,repeat=6)): + for i, sextuple in enumerate(product(basis, repeat=6)): if i % n_proc == child_id: he = req_cy(basis,r_matrix,fvars,_Nk_ij,id_anyon,sextuple) if he: @@ -244,14 +247,15 @@ cdef get_reduced_hexagons(factory, tuple mp_params): worker_results.append(red) cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, id_anyon, zero, tuple nonuple, bint prune=False): - """ - Given an FMatrix factory and a nonuple, return a pentagon equation as a polynomial object + r""" + Given an FMatrix factory and a nonuple, return a pentagon equation + as a polynomial object. """ a, b, c, d, e, f, g, k, l = nonuple - cdef lhs = _fmat(fvars,Nk_ij,id_anyon,f,c,d,e,g,l)*_fmat(fvars,Nk_ij,id_anyon,a,b,l,e,f,k) + lhs = _fmat(fvars,Nk_ij,id_anyon,f,c,d,e,g,l) * _fmat(fvars,Nk_ij,id_anyon,a,b,l,e,f,k) if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information return zero - cdef rhs = zero + rhs = zero for h in basis: rhs += _fmat(fvars,Nk_ij,id_anyon,a,b,c,g,f,h)*_fmat(fvars,Nk_ij,id_anyon,a,h,d,e,g,k)*_fmat(fvars,Nk_ij,id_anyon,b,c,d,k,h,l) return lhs - rhs @@ -260,8 +264,8 @@ cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, id_anyon, zero, t @cython.nonecheck(False) @cython.cdivision(True) cdef get_reduced_pentagons(factory, tuple mp_params): - """ - Set up and reduce the pentagon equations corresponding to this worker + r""" + Set up and reduce the pentagon equations corresponding to this worker. """ #Set up multiprocessing parameters global worker_results @@ -286,9 +290,9 @@ cdef get_reduced_pentagons(factory, tuple mp_params): for i in range(len(basis)**9): nonuple = next(it) if i % n_proc == child_id: - pe = feq_cy(basis,fvars,_Nk_ij,id_anyon,zero,nonuple,prune=True) + pe = feq_cy(basis, fvars, _Nk_ij, id_anyon, zero, nonuple, prune=True) if pe: - red = reduce_poly_dict(pe.dict(),_nnz,_ks,one) + red = reduce_poly_dict(pe.dict(), _nnz, _ks, one) #Avoid pickling cyclotomic coefficients red = _flatten_coeffs(red) @@ -296,8 +300,8 @@ cdef get_reduced_pentagons(factory, tuple mp_params): worker_results.append(red) cpdef update_reduce(factory, tuple eq_tup): - """ - Substitute known values, known squares, and reduce! + r""" + Substitute known values, known squares, and reduce. EXAMPLES:: @@ -334,7 +338,7 @@ cpdef update_reduce(factory, tuple eq_tup): worker_results.append(red) cpdef compute_gb(factory, tuple args): - """ + r""" Compute the reduced Groebner basis for given equations iterable. EXAMPLES:: @@ -345,15 +349,17 @@ cpdef compute_gb(factory, tuple args): ....: set_start_method('fork') # context can be set only once ....: except RuntimeError: ....: pass - sage: f.get_defining_equations('hexagons',worker_pool=Pool(),output=False) - sage: partition = f._partition_eqns() + sage: f.get_defining_equations('hexagons',worker_pool=Pool(),output=False) # long time + sage: partition = f._partition_eqns() # long time Partitioned 261 equations into 57 components of size: - [24, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1] + [24, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 1, 1, 1, 1, 1] sage: from sage.combinat.root_system.fast_parallel_fmats_methods import compute_gb, collect_eqns - sage: args = (partition[(29, 40, 83, 111, 148, 154)], "degrevlex") - sage: compute_gb(f, args) + sage: args = (partition[(29, 40, 83, 111, 148, 154)], "degrevlex") # long time + sage: compute_gb(f, args) # long time sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs - sage: [f._tup_to_fpoly(_unflatten_coeffs(f._field,t)) for t in collect_eqns(0)] + sage: [f._tup_to_fpoly(_unflatten_coeffs(f._field,t)) for t in collect_eqns(0)] # long time [fx83*fx154 - fx111, fx29^2*fx154 + fx29, fx29*fx148 - fx40, @@ -369,12 +375,12 @@ cpdef compute_gb(factory, tuple args): cdef list eqns, sorted_vars eqns, term_order = args #Define smaller poly ring in component vars - sorted_vars = list() + sorted_vars = [] cdef tuple eq_tup cdef int fx for eq_tup in eqns: - for fx in variables(eq_tup): - sorted_vars.append(fx) + for fx in variables(eq_tup): + sorted_vars.append(fx) sorted_vars = sorted(set(sorted_vars)) cdef MPolynomialRing_libsingular R = PolynomialRing(factory._FR.field(),len(sorted_vars),'a',order=term_order) @@ -401,10 +407,10 @@ cpdef compute_gb(factory, tuple args): worker_results.append(t) cpdef update_child_fmats(factory, tuple data_tup): - """ + r""" One-to-all communication used to update FMatrix object after each triangular elim step. We must update the algorithm's state values. These are: - _fvars, _solved, _ks, _var_degs, _nnz, and _kp. + ``_fvars``, ``_solved``, ``_ks``, ``_var_degs``, ``_nnz``, and ``_kp``. TESTS:: @@ -438,7 +444,7 @@ cpdef update_child_fmats(factory, tuple data_tup): ################ cpdef collect_eqns(proc): - """ + r""" Helper function for returning processed results back to parent process. Trivial reducer: simply collects objects with the same key in the worker. @@ -462,8 +468,8 @@ cpdef collect_eqns(proc): """ #Discard the zero polynomial global worker_results - reduced = set(worker_results)-set([tuple()]) - worker_results = list() + reduced = set(worker_results) - set([tuple()]) + worker_results = [] return list(reduced) ############################## @@ -503,7 +509,7 @@ cpdef executor(params): of object `X` in each worker, so we may construct references to forked copies of `X` using an ``id`` obtained in the parent process. - TESTS: + TESTS:: sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor sage: fmats = FMatrix(FusionRing("A1",3)) @@ -529,8 +535,8 @@ cpdef executor(params): #################### cdef feq_verif(factory, fvars, Nk_ij, id_anyon, tuple nonuple, float tol=5e-8): - """ - Check the pentagon equation corresponding to the given nonuple + r""" + Check the pentagon equation corresponding to the given nonuple. """ global worker_results a, b, c, d, e, f, g, k, l = nonuple @@ -548,8 +554,9 @@ cdef feq_verif(factory, fvars, Nk_ij, id_anyon, tuple nonuple, float tol=5e-8): @cython.nonecheck(False) @cython.cdivision(True) cdef pent_verify(factory, tuple mp_params): - """ - Generate all the pentagon equations assigned to this process, and reduce them + r""" + Generate all the pentagon equations assigned to this process, + and reduce them. """ child_id, n_proc, verbose = mp_params cdef float t0 @@ -560,8 +567,9 @@ cdef pent_verify(factory, tuple mp_params): Nk_ij = factory._FR.Nk_ij cdef dict fvars = factory._fvars id_anyon = factory._FR.one() - for i, nonuple in enumerate(product(factory._FR.basis(),repeat=9)): + for i, nonuple in enumerate(product(factory._FR.basis(), repeat=9)): if i % n_proc == child_id: - feq_verif(factory,fvars,Nk_ij,id_anyon,nonuple) + feq_verif(factory,fvars,Nk_ij,id_anyon,nonuple) if i % 50000000 == 0 and i and verbose: - print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) + print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) + diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd index 4dd122a7fbf..a032a509637 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd @@ -1,3 +1,4 @@ cpdef _unflatten_entries(factory, list entries) cpdef executor(params) cpdef collect_results(proc) + diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index a40a5f136c1..4fca46e377f 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -24,14 +24,14 @@ worker_results = list() ############### cpdef mid_sig_ij(fusion_ring,row,col,a,b): - """ - Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two strands - in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j - of trees b -> xj # yj -> (a # a) # (a # a) + r""" + Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two + strands in the tree b -> xi # yi -> (a # a) # (a # a), which results in + a sum over j of trees b -> xj # yj -> (a # a) # (a # a) .. WARNING:: - This method assumes F-matrices are orthogonal + This method assumes F-matrices are orthogonal. EXAMPLES:: @@ -60,7 +60,14 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): sage: FR.get_computational_basis(a2,a2,4) # long time [(a2, a2), (a2, a0), (a0, a2)] sage: to_opt(mid_sig_ij(FR,(a2, a0),(a2, a2),a2,a2)) # long time - -2024728666370660589/10956322398441542221*a1^30 - 34142146914395596291/21912644796883084442*a1^28 - 21479437628091413631/21912644796883084442*a1^26 + 260131910217202103829/21912644796883084442*a1^24 + 69575612911670713183/10956322398441542221*a1^22 + 25621808994337724689/1992058617898462222*a1^20 - 1975139725303994650417/21912644796883084442*a1^18 - 1315664901396537703585/21912644796883084442*a1^16 - 2421451803369354765026/10956322398441542221*a1^14 - 5963323855935165859057/21912644796883084442*a1^12 - 4477124943233705460859/21912644796883084442*a1^10 - 2001454824483021618178/10956322398441542221*a1^8 - 2120319455379289595185/21912644796883084442*a1^6 - 15722612944437234961/755608441271830498*a1^4 - 39862668562651453480/10956322398441542221*a1^2 - 6967145776903524195/10956322398441542221 + -2024728666370660589/10956322398441542221*a1^30 - 34142146914395596291/21912644796883084442*a1^28 + - 21479437628091413631/21912644796883084442*a1^26 + 260131910217202103829/21912644796883084442*a1^24 + + 69575612911670713183/10956322398441542221*a1^22 + 25621808994337724689/1992058617898462222*a1^20 + - 1975139725303994650417/21912644796883084442*a1^18 - 1315664901396537703585/21912644796883084442*a1^16 + - 2421451803369354765026/10956322398441542221*a1^14 - 5963323855935165859057/21912644796883084442*a1^12 + - 4477124943233705460859/21912644796883084442*a1^10 - 2001454824483021618178/10956322398441542221*a1^8 + - 2120319455379289595185/21912644796883084442*a1^6 - 15722612944437234961/755608441271830498*a1^4 + - 39862668562651453480/10956322398441542221*a1^2 - 6967145776903524195/10956322398441542221 sage: FR = FusionRing("G2",2) # long time sage: FR.fusion_labels("g",inject_variables=True) # long time sage: FR.get_computational_basis(g1,g2,4) # long time @@ -96,7 +103,7 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): .. WARNING:: - This method assumes F-matrices are orthogonal + This method assumes F-matrices are orthogonal. EXAMPLES:: @@ -127,7 +134,14 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): sage: FR.get_computational_basis(a2,a2,3) # long time [(a2,), (a0,)] sage: to_opt(odd_one_out_ij(FR,a0,a2,a2,a2)) # long time - 6341990144855406911/21912644796883084442*a1^30 + 47313529044493641571/21912644796883084442*a1^28 - 6964289120109414595/10956322398441542221*a1^26 - 406719371329322780627/21912644796883084442*a1^24 + 87598732372849355687/10956322398441542221*a1^22 - 456724726845194775/19723352652460022*a1^20 + 3585892725441116840515/21912644796883084442*a1^18 - 645866255979227573282/10956322398441542221*a1^16 + 7958479159087829772639/21912644796883084442*a1^14 + 789748976956837633826/10956322398441542221*a1^12 + 3409710648897945752185/21912644796883084442*a1^10 + 903956381582048110980/10956322398441542221*a1^8 + 192973084151342020307/21912644796883084442*a1^6 - 9233312083438019435/755608441271830498*a1^4 + 667869266552877781/10956322398441542221*a1^2 + 17644302696056968099/21912644796883084442 + 6341990144855406911/21912644796883084442*a1^30 + 47313529044493641571/21912644796883084442*a1^28 + - 6964289120109414595/10956322398441542221*a1^26 - 406719371329322780627/21912644796883084442*a1^24 + + 87598732372849355687/10956322398441542221*a1^22 - 456724726845194775/19723352652460022*a1^20 + + 3585892725441116840515/21912644796883084442*a1^18 - 645866255979227573282/10956322398441542221*a1^16 + + 7958479159087829772639/21912644796883084442*a1^14 + 789748976956837633826/10956322398441542221*a1^12 + + 3409710648897945752185/21912644796883084442*a1^10 + 903956381582048110980/10956322398441542221*a1^8 + + 192973084151342020307/21912644796883084442*a1^6 - 9233312083438019435/755608441271830498*a1^4 + + 667869266552877781/10956322398441542221*a1^2 + 17644302696056968099/21912644796883084442 sage: FR = FusionRing("G2",2) # long time sage: FR.fusion_labels("g",inject_variables=True) # long time sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) @@ -155,7 +169,7 @@ odd_one_out_ij = cached_function(odd_one_out_ij, name='odd_one_out_ij') @cython.nonecheck(False) @cython.cdivision(True) cdef sig_2k(fusion_ring, tuple args): - """ + r""" Compute entries of the 2k-th braid generator """ #Pre-compute common parameters for efficiency @@ -163,6 +177,7 @@ cdef sig_2k(fusion_ring, tuple args): _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() + cdef int child_id, n_proc child_id, n_proc, fn_args = args k, a, b, n_strands = fn_args cdef int ctr = -1 @@ -174,9 +189,9 @@ cdef sig_2k(fusion_ring, tuple args): cdef set coords = set() cdef int i #Avoid pickling cyclotomic field element objects - must_flatten_coeff = fusion_ring.fvars_field() != QQbar + cdef bint must_flatten_coeff = fusion_ring.fvars_field() != QQbar for i in range(dim): - for f,e,q in product(fusion_ring.basis(),repeat=3): + for f,e,q in product(fusion_ring.basis(), repeat=3): #Distribute work amongst processes ctr += 1 if ctr % n_proc != child_id: @@ -184,7 +199,8 @@ cdef sig_2k(fusion_ring, tuple args): #Compute appropriate possible nonzero row index nnz_pos = list(comp_basis[i]) - nnz_pos[k-1:k+1] = f,e + nnz_pos[k-1] = f + nnz_pos[k] = e #Handle the special case k = 1 if k > 1: nnz_pos[n_strands//2+k-2] = q @@ -223,14 +239,14 @@ cdef sig_2k(fusion_ring, tuple args): if must_flatten_coeff: entry = entry.list() - worker_results.append(((basis_dict[nnz_pos],i), entry)) + worker_results.append(((basis_dict[nnz_pos], i), entry)) @cython.nonecheck(False) @cython.cdivision(True) cdef odd_one_out(fusion_ring, tuple args): """ - Compute entries of the rightmost braid generator, in case we have an odd number - of strands + Compute entries of the rightmost braid generator, in case we have an + odd number of strands. """ #Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars @@ -238,16 +254,17 @@ cdef odd_one_out(fusion_ring, tuple args): one = fusion_ring.one() global worker_results + cdef int child_id, n_proc child_id, n_proc, fn_args = args a, b, n_strands = fn_args - cdef ctr = -1 + cdef int ctr = -1 #Get computational basis - comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) - basis_dict = { elt : i for i, elt in enumerate(comp_basis) } + comp_basis = fusion_ring.get_computational_basis(a, b, n_strands) + basis_dict = {elt: i for i, elt in enumerate(comp_basis)} dim = len(comp_basis) #Avoid pickling cyclotomic field element objects - must_flatten_coeff = fusion_ring.fvars_field() != QQbar + cdef bint must_flatten_coeff = fusion_ring.fvars_field() != QQbar for i in range(dim): for f, q in product(fusion_ring.basis(),repeat=2): #Distribute work amongst processes @@ -274,7 +291,7 @@ cdef odd_one_out(fusion_ring, tuple args): if must_flatten_coeff: entry = entry.list() - worker_results.append(((basis_dict[nnz_pos],i), entry)) + worker_results.append(((basis_dict[nnz_pos], i), entry)) continue top_left = m[0] if n_strands > 5: @@ -321,7 +338,7 @@ cpdef collect_results(proc): #Discard the zero polynomial global worker_results reduced = worker_results - worker_results = list() + worker_results = [] return reduced ############################## @@ -384,8 +401,8 @@ cpdef _unflatten_entries(fusion_ring, list entries): Restore cyclotomic coefficient object from its tuple of rational coefficients representation. - Used to circumvent pickling issue introduced by PARI settigs in trac - ticket #30537 + Used to circumvent pickling issue introduced by PARI settigs + in :trac:`30537`. EXAMPLES:: @@ -400,7 +417,7 @@ cpdef _unflatten_entries(fusion_ring, list entries): """ F = fusion_ring.fvars_field() fm = fusion_ring.fmats - must_unflatten = F != QQbar - if must_unflatten: + if F != QQbar: for i, (coord, entry) in enumerate(entries): entries[i] = (coord, F(entry)) + diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 09ca6d348bf..53a5a1438c2 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -14,7 +14,6 @@ from itertools import product, zip_longest from multiprocessing import Pool, set_start_method from sage.combinat.q_analogues import q_int -import sage.combinat.root_system.f_matrix as FMatrix from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import ( collect_results, executor, _unflatten_entries @@ -385,11 +384,12 @@ def test_braid_representation(self, max_strands=6): - ``max_strands`` -- (default: 6): maximum number of braid group strands Create a braid group representation using :meth:`get_braid_generators` - and confirms the braid relations. This test indirectly partially verifies - the correctness of the orthogonal F-matrix solver. If the code were - incorrect the method would not be deterministic because the fusing anyon - is chosen randomly. (A different choice is made for each number of strands tested.) - However the doctest is deterministic since it will always return True. + and confirms the braid relations. This test indirectly partially + verifies the correctness of the orthogonal F-matrix solver. If the + code were incorrect the method would not be deterministic because the + fusing anyon is chosen randomly. (A different choice is made for each + number of strands tested.) However the doctest is deterministic since + it will always return ``True``. EXAMPLES:: @@ -401,7 +401,7 @@ def test_braid_representation(self, max_strands=6): True """ if not self.is_multiplicity_free(): # Braid group representation is not available if self is not multiplicity free - return True + raise NotImplementedError("only implemented for multiplicity free fusion rings") b = self.basis() results = [] #Test with different numbers of strands @@ -552,9 +552,9 @@ def fvars_field(self): 2*a0 + 3*a2 sage: comp_basis, sig = A13.get_braid_generators(a2,a2,3,verbose=False) # long time (<3s) sage: A13.fvars_field() # long time - Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 + Number Field in a with defining polynomial y^32 - ... - 500*y^2 + 25 sage: a2.q_dimension().parent() # long time - Number Field in a with defining polynomial y^32 - 8*y^30 + 18*y^28 - 44*y^26 + 93*y^24 - 56*y^22 + 2132*y^20 - 1984*y^18 + 19738*y^16 - 28636*y^14 + 77038*y^12 - 109492*y^10 + 92136*y^8 - 32300*y^6 + 5640*y^4 - 500*y^2 + 25 + Number Field in a with defining polynomial y^32 - ... - 500*y^2 + 25 sage: A13.field() Cyclotomic Field of order 40 and degree 16 @@ -1050,17 +1050,17 @@ def D_minus(self, base_coercion=True): return self._basecoer(ret) def is_multiplicity_free(self): - """ - Return True if the fusion multiplicities - :meth:`Nk_ij` are bounded by 1. The :class:`FMatrix` - is available only for multiplicity free instances of - :class:`FusionRing`. + r""" + Return ``True`` if the fusion multiplicities + :meth:`Nk_ij` are bounded by 1. + + The :class:`FMatrix` is available only for multiplicity free + instances of :class:`FusionRing`. EXAMPLES:: sage: [FusionRing(ct,k).is_multiplicity_free() for ct in ("A1","A2","B2","C3") for k in (1,2,3)] [True, True, True, True, True, False, True, True, False, True, False, False] - """ ct = self.cartan_type() k = self.fusion_level() @@ -1080,8 +1080,8 @@ def is_multiplicity_free(self): ################################### def get_computational_basis(self,a,b,n_strands): - """ - Return the so-called computational basis for `\\text{Hom}(b, a^n)`. + r""" + Return the so-called computational basis for `\text{Hom}(b, a^n)`. INPUT: @@ -1089,8 +1089,8 @@ def get_computational_basis(self,a,b,n_strands): - ``b`` -- another basis element - ``n_strands`` -- the number of strands for a braid group - Let `n=` ``n_strands`` and let `k` be the greatest integer `\leqslant n/2`. - The braid group acts on `\\text{Hom}(b,a^n)`. This action + Let `n=` ``n_strands`` and let `k` be the greatest integer `\leq n/2`. + The braid group acts on `\text{Hom}(b,a^n)`. This action is computed in :meth:`get_braid_generators`. This method returns the computational basis in the form of a list of fusion trees. Each tree is represented by an `(n-2)`-tuple @@ -1104,18 +1104,18 @@ def get_computational_basis(self,a,b,n_strands): .. MATH:: - \\begin{array}{l} - b \\in l_{k-2} \otimes m_{k},\\\\ - l_{k-2} \\in l_{k-3} \otimes m_{k-1},\\\\ - \\cdots,\\\\ - l_2 \\in l_1\otimes m_3,\\\\ - l_1 \\in m_1\otimes m_2. + \begin{array}{l} + b \in l_{k-2} \otimes m_{k},\\ + l_{k-2} \in l_{k-3} \otimes m_{k-1},\\ + \cdots,\\ + l_2 \in l_1 \otimes m_3,\\ + l_1 \in m_1 \otimes m_2. \end{array} - where `z \\in x\otimes y` means `N_{xy}^z\\neq 0`. + where `z \in x \otimes y` means `N_{xy}^z \neq 0`. As a computational device when ``n_strands`` is odd, we pad the - vector `(m_1,\ldots,m_k)` with an additional `m_{k+1}` equal to `a`. + vector `(m_1, \ldots, m_k)` with an additional `m_{k+1}` equal to `a`. However, this `m_{k+1}` does *not* appear in the output of this method. The following example appears in Section 3.1 of [CW2015]_. @@ -1148,9 +1148,9 @@ def _get_trees(fr,top_row,root): @lazy_attribute def fmats(self): - """ - Construct an FMatrix factory to solve the pentagon relations and - organize the resulting F-symbols. + r""" + Construct an :class:`FMatrix` factory to solve the pentagon relations + and organize the resulting F-symbols. We only need this attribute to compute braid group representations. @@ -1160,34 +1160,37 @@ def fmats(self): sage: A15.fmats F-Matrix factory for The Fusion Ring of Type A1 and level 5 with Integer Ring coefficients """ - return FMatrix.FMatrix(self) + from sage.combinat.root_system.f_matrix import FMatrix + return FMatrix(self) - def _emap(self,mapper,input_args,worker_pool=None): - """ - Apply the given mapper to each element of the given input iterable and - return the results (with no duplicates) in a list. This method applies the - mapper in parallel if a worker_pool is provided. + def _emap(self, mapper, input_args, worker_pool=None): + r""" + Apply the given mapper to each element of the given input iterable + and return the results (with no duplicates) in a list. INPUT: - - ``mapper`` -- a string specifying the name of a function defined in the - ``fast_parallel_fusion_ring_braid_repn`` module. + - ``mapper`` -- a string specifying the name of a function defined + in the ``fast_parallel_fusion_ring_braid_repn`` module - ``input_args`` -- a tuple of arguments to be passed to mapper + This method applies the mapper in parallel if a ``worker_pool`` + is provided. + .. NOTE:: - If ``worker_pool`` is not provided, function maps and reduces on a - single process. - If ``worker_pool`` is provided, the function attempts to determine - whether it should use multiprocessing based on the length of the - input iterable. If it can't determine the length of the input - iterable then it uses multiprocessing with the default chunksize of - `1` if chunksize is not explicitly provided. + If ``worker_pool`` is not provided, function maps and reduces on + a single process. If ``worker_pool`` is provided, the function + attempts to determine whether it should use multiprocessing + based on the length of the input iterable. If it cannot determine + the length of the input iterable then it uses multiprocessing + with the default chunksize of `1` if chunksize is not + explicitly provided. EXAMPLES:: sage: FR = FusionRing("A1",4) - sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) + sage: FR.fusion_labels(['idd','one','two','three','four'], inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time sage: len(FR._emap('sig_2k',(1,one,one,5))) # long time 13 @@ -1211,7 +1214,7 @@ def _emap(self,mapper,input_args,worker_pool=None): if no_mp: results = collect_results(0) else: - results = list() + results = [] for worker_results in worker_pool.imap_unordered(collect_results,range(worker_pool._processes),chunksize=1): results.extend(worker_results) return results @@ -1225,28 +1228,31 @@ def get_braid_generators(self, warm_start="", use_mp=True, verbose=True): - """ - Compute generators of the Artin braid group on `n=` ``n_strands`` - strands. If `a` = ``fusing_anyon`` and `b` = ``total_charge_anyon`` - the generators are endomorphisms of `\\text{Hom}(b, a^n)`. + r""" + Compute generators of the Artin braid group on ``n_strands`` strands. + + If `a = ` ``fusing_anyon`` and `b = ` ``total_charge_anyon`` + the generators are endomorphisms of `\text{Hom}(b, a^n)`. INPUT: - - ``fusing_anyon`` -- a basis element of self - - ``total_charge_anyon`` -- a basis element of self + - ``fusing_anyon`` -- a basis element of ``self`` + - ``total_charge_anyon`` -- a basis element of ``self`` - ``n_strands`` -- a positive integer greater than 2 - - ``checkpoint`` -- (optional) a boolean indicating whether the - F-matrix solver should pickle checkpoints. + - ``checkpoint`` -- (default: ``False``) a boolean indicating + whether the F-matrix solver should pickle checkpoints - ``save_results`` -- (optional) a string indicating the name of - a file in which to pickle computed F-symbols for later use. + a file in which to pickle computed F-symbols for later use - ``warm_start`` -- (optional) a string indicating the name of a pickled checkpoint file to "warm" start the F-matrix solver. The pickle may be a checkpoint generated by the solver, or a file containing solver results. If all F-symbols are known, we don't run the solver again. - - ``use_mp`` -- (optional) a boolean indicating whether to use - multiprocessing to speed up the computation. This is highly - recommended. + - ``use_mp`` -- (default: ``True``) a boolean indicating whether + to use multiprocessing to speed up the computation; this is + highly recommended + - ``verbose`` -- (default: ``True``) boolean indicating whether + to be verbose with the computation For more information on the optional parameters, see :meth:`find_orthogonal_solution` of the :class:`FMatrix` module. @@ -1273,8 +1279,8 @@ def get_braid_generators(self, some cases these will be represented as sparse matrices. In the following example we compute a 5-dimensional braid group - representation on 5 strands associated to the spin representation in the - modular tensor category `SU(2)_4 \cong SO(3)_2`. + representation on 5 strands associated to the spin representation + in the modular tensor category `SU(2)_4 \cong SO(3)_2`. EXAMPLES:: @@ -1291,9 +1297,9 @@ def get_braid_generators(self, True sage: len(comp_basis) == 5 # long time True - """ - assert int(n_strands) > 2, "The number of strands must be an integer greater than 2" + if n_strands < 3: + raise ValueError("the number of strands must be an integer at least 3") #Construct associated FMatrix object and solve for F-symbols if self.fmats._chkpt_status < 7: self.fmats.find_orthogonal_solution(checkpoint=checkpoint, @@ -1315,18 +1321,17 @@ def get_braid_generators(self, comp_basis = self.get_computational_basis(a,b,n_strands) d = len(comp_basis) if verbose: - print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d,n_strands)) + print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d, n_strands)) #Compute diagonal odd-indexed generators using the 3j-symbols - gens = { 2*i+1 : diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2) } + gens = {2*i+1: diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2)} #Compute even-indexed generators using F-matrices - for k in range(1,n_strands//2): + for k in range(1, n_strands//2): entries = self._emap('sig_2k',(k,a,b,n_strands),pool) #Build cyclotomic field element objects from tuple of rationals repn _unflatten_entries(self, entries) - gens[2*k] = matrix(dict(entries)) #If n_strands is odd, we compute the final generator @@ -1335,15 +1340,16 @@ def get_braid_generators(self, #Build cyclotomic field element objects from tuple of rationals repn _unflatten_entries(self, entries) - gens[n_strands-1] = matrix(dict(entries)) return comp_basis, [gens[k] for k in sorted(gens)] - def gens_satisfy_braid_gp_rels(self,sig): - """ - Return True if the matrices in the list ``sig`` satisfy - the braid relations. This if `n` is the cardinality of ``sig``, this + def gens_satisfy_braid_gp_rels(self, sig): + r""" + Return ``True`` if the matrices in the list ``sig`` satisfy + the braid relations. + + This if `n` is the cardinality of ``sig``, this confirms that these matrices define a representation of the Artin braid group on `n+1` strands. Tests correctness of :meth:`get_braid_generators`. diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 62f7401e7b0..7ca654259d3 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -19,3 +19,4 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFiel cdef tuple _flatten_coeffs(tuple eq_tup) cpdef tuple _unflatten_coeffs(field, tuple eq_tup) cdef int has_appropriate_linear_term(tuple eq_tup) + diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 24baf59fd46..5d662efc772 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -13,9 +13,9 @@ Arithmetic Engine for polynomials as tuples ########### cpdef inline tuple poly_to_tup(MPolynomial_libsingular poly): - """ + r""" Convert a polynomial object into the internal representation as tuple of - (ETuple exp, NumberFieldElement coeff) pairs + ``(ETuple exp, NumberFieldElement coeff)`` pairs. EXAMPLES:: @@ -34,14 +34,12 @@ cpdef inline MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_ Inverse of :meth:`poly_to_tup`: - ``poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup`` and - - ``tup_to_poly(poly_to_tup(eq), eq.parent()) == eq``. + - ``poly_to_tup(tup_to_poly(eq_tup, ring)) == eq_tup`` and + - ``tup_to_poly(poly_to_tup(eq), eq.parent()) == eq``. .. NOTE:: - Assumes ``parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup``. - + Assumes ``all(parent.ngens() == len(exp_tup) for exp_tup, c in eq_tup)``. This method is meant for internal use. .. WARNING:: @@ -62,7 +60,7 @@ cpdef inline MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_ sage: _tup_to_poly(poly_to_tup(poly), parent=R) == poly True - TESTS: + TESTS:: sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, _tup_to_poly sage: R. = PolynomialRing(CyclotomicField(20)) @@ -77,27 +75,26 @@ cpdef inline MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_ return parent._element_constructor_(dict(eq_tup), check=False) cdef inline tuple _flatten_coeffs(tuple eq_tup): - """ + r""" Flatten cyclotomic coefficients to a representation as a tuple of rational coefficients. This is used to avoid pickling cyclotomic coefficient objects, which fails with new PARI settings introduced in trac ticket #30537 """ - cdef list flat = list() - cdef ETuple exp + cdef list flat = [] cdef NumberFieldElement_absolute cyc_coeff for exp, cyc_coeff in eq_tup: flat.append((exp, tuple(cyc_coeff._coefficients()))) return tuple(flat) cpdef tuple _unflatten_coeffs(field, tuple eq_tup): - """ + r""" Restore cyclotomic coefficient object from its tuple of rational coefficients representation. - Used to circumvent pickling issue introduced by PARI settigs in trac - ticket #30537 + Used to circumvent pickling issue introduced by PARI settigs + in :trac:`30537`. EXAMPLES:: @@ -112,8 +109,7 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): sage: _unflatten_coeffs(fm.field(), flat_poly_tup) == poly_to_tup(p) True """ - cdef list unflat = list() - cdef ETuple exp + cdef list unflat = [] for exp, coeff_tup in eq_tup: unflat.append((exp, field(list(coeff_tup)))) return tuple(unflat) @@ -123,7 +119,7 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): ################################# cdef inline int has_appropriate_linear_term(tuple eq_tup): - """ + r""" Determine whether the given tuple of pairs (of length 2) contains an *appropriate* linear term. @@ -140,8 +136,9 @@ cdef inline int has_appropriate_linear_term(tuple eq_tup): Otherwise, the method returns -1. """ - max_var = variables(eq_tup)[0] + max_var = degrees(eq_tup).nonzero_positions()[0] cdef ETuple m + cdef int i for i in range(2): m = eq_tup[i][0] if m._nonzero == 1 and m._data[1] == 1 and m._data[0] == max_var and eq_tup[(i+1) % 2][0][max_var] == 0: @@ -196,7 +193,7 @@ cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): sage: from sage.rings.polynomial.polydict import ETuple sage: K = CyclotomicField(56) sage: poly_tup = ((ETuple([0,3,0,2]),K(2)), (ETuple([0,1,0,1]),K(-1)), (ETuple([0,0,0,0]),K(-2/3))) - sage: idx_map = { 1 : 0, 3 : 1 } + sage: idx_map = {1: 0, 3: 1} sage: resize(poly_tup,idx_map,2) (((3, 2), 2), ((1, 1), -1), ((0, 0), -2/3)) @@ -213,8 +210,8 @@ cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): cdef NumberFieldElement_absolute c cdef list resized = list() for exp, c in eq_tup: - new_e = ETuple({ idx_map[pos] : d for pos, d in exp.sparse_iter() }, nvars) - resized.append((new_e,c)) + new_e = ETuple({idx_map[pos]: d for pos, d in exp.sparse_iter()}, nvars) + resized.append((new_e, c)) return tuple(resized) ########################### @@ -222,20 +219,22 @@ cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): ########################### cdef inline ETuple degrees(tuple poly_tup): - """ - Return the maximal degree of each variable in the polynomial + r""" + Return the maximal degree of each variable in the polynomial. """ #Deal with the empty tuple, representing the zero polynomial - if not poly_tup: return ETuple() + if not poly_tup: + return ETuple() cdef ETuple max_degs, exp - max_degs = poly_tup[0][0] - for exp, c in poly_tup[1:]: - max_degs = max_degs.emax(exp) + cdef int i + max_degs = ( poly_tup[0])[0] + for i in range(1, len(poly_tup)): + max_degs = max_degs.emax( ( poly_tup[i])[0]) return max_degs cpdef ETuple get_variables_degrees(list eqns): - """ - Find maximum degrees for each variable in equations + r""" + Find maximum degrees for each variable in equations. EXAMPLES:: @@ -246,12 +245,13 @@ cpdef ETuple get_variables_degrees(list eqns): sage: get_variables_degrees([poly_to_tup(p) for p in polys]) (2, 1, 3) """ - if not eqns: return ETuple([]) - cdef tuple eq_tup + if not eqns: + return ETuple([]) cdef ETuple max_deg + cdef int i max_deg = degrees(eqns[0]) - for eq_tup in eqns[1:]: - max_deg = max_deg.emax(degrees(eq_tup)) + for i in range(1, len(eqns)): + max_deg = max_deg.emax(degrees( (eqns[i]) )) return max_deg cpdef list variables(tuple eq_tup): @@ -275,7 +275,7 @@ cpdef list variables(tuple eq_tup): return degrees(eq_tup).nonzero_positions() cpdef constant_coeff(tuple eq_tup): - """ + r""" Return the constant coefficient of the polynomial represented by given tuple. @@ -293,8 +293,8 @@ cpdef constant_coeff(tuple eq_tup): """ cdef ETuple exp for exp, coeff in eq_tup: - if exp.is_constant(): - return coeff + if exp.is_constant(): + return coeff return 0 cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): @@ -311,7 +311,7 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): x + 4*y + 9*z """ cdef ETuple exp - cdef list new_tup = list() + cdef list new_tup = [] for exp, coeff in eq_tup: new_tup.append((exp, coeff_map(coeff))) return tuple(new_tup) @@ -409,7 +409,7 @@ cdef dict remove_gcf(dict eq_dict, ETuple nonz): common_powers = nonz for exp, c in eq_dict.items(): common_powers = common_powers.emin(exp) - cdef dict ret = dict() + cdef dict ret = {} for exp, c in eq_dict.items(): ret[exp.esub(common_powers)] = c return ret @@ -422,11 +422,13 @@ cdef tuple to_monic(dict eq_dict, one): Here, the leading coefficient is chosen according to the degree reverse lexicographic ordering (default for multivariate polynomial rings). """ - if not eq_dict: return tuple() + if not eq_dict: + return () cdef list ord_monoms = sorted(eq_dict, key=monom_sortkey) cdef ETuple lm = ord_monoms[-1] cdef NumberFieldElement_absolute lc = eq_dict[lm] - if not lc: return tuple() + if not lc: + return () cdef list ret = [(lm, one)] inv_lc = lc.inverse_of_unit() cdef int i, n @@ -440,7 +442,8 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFiel Return a tuple describing a monic polynomial with no known nonzero gcf and no known squares. """ - if not eq_dict: return tuple() + if not eq_dict: + return () cdef dict sq_rmvd = subs_squares(eq_dict, known_sq) cdef dict gcf_rmvd = remove_gcf(sq_rmvd, nonz) return to_monic(gcf_rmvd, one) @@ -454,14 +457,14 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): Pre-compute powers of known values for efficiency when preparing to substitute into a list of polynomials. - INPUTS: + INPUT: - - ``max_deg`` -- an ``ETuple`` indicating the maximal degree of - each variable. - - ``val_dict`` -- a dictionary of ``(var_idx, poly_tup)`` key-value - pairs. - - ``poly_tup`` -- a tuple of (ETuple, coeff) pairs reperesenting a - multivariate polynomial. + - ``max_deg`` -- an ``ETuple`` indicating the maximal degree of + each variable + - ``val_dict`` -- a dictionary of ``(var_idx, poly_tup)`` key-value + pairs + - ``poly_tup`` -- a tuple of ``(ETuple, coeff)`` pairs reperesenting a + multivariate polynomial EXAMPLES:: @@ -482,22 +485,23 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): (((0, 4, 0), 1),), (((0, 6, 0), 1),)]} """ - if not max_deg: return dict() + if not max_deg: + return {} assert max(max_deg.nonzero_values(sort=False)) <= 100, "NotImplementedError: Cannot substitute for degree larger than 100" - max_deg = max_deg.emin(ETuple({ idx : 100 for idx in val_dict }, len(max_deg))) + max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) cdef dict known_powers #Get polynomial unit as tuple to initialize list elements cdef tuple one_tup = ((max_deg._new(), one),) cdef int d, power, var_idx - known_powers = { var_idx : [one_tup]*(d+1) for var_idx, d in max_deg.sparse_iter() } + known_powers = {var_idx: [one_tup]*(d+1) for var_idx, d in max_deg.sparse_iter()} for var_idx, d in max_deg.sparse_iter(): for power in range(d): - known_powers[var_idx][power+1] = tup_mul(known_powers[var_idx][power],val_dict[var_idx]) + known_powers[var_idx][power+1] = tup_mul(known_powers[var_idx][power], val_dict[var_idx]) return known_powers cdef dict subs(tuple poly_tup, dict known_powers, one): """ - Substitute given variables into a polynomial tuple + Substitute given variables into a polynomial tuple. """ cdef dict subbed = {} cdef ETuple exp, m, shifted_exp @@ -513,24 +517,24 @@ cdef dict subs(tuple poly_tup, dict known_powers, one): for m, c in temp: shifted_exp = exp.eadd(m) if shifted_exp in subbed: - subbed[shifted_exp] += coeff*c + subbed[shifted_exp] += coeff * c else: - subbed[shifted_exp] = coeff*c + subbed[shifted_exp] = coeff * c return subbed cdef tuple tup_mul(tuple p1, tuple p2): + r""" + Multiplication of two polynomial tuples using schoolbook multiplication. """ - Multiplication of two tuples... may have to make this faster - """ - cdef dict prod = dict() + cdef dict prod = {} cdef ETuple xi, yj, shifted_exp for xi, ai in p1: for yj, bj in p2: shifted_exp = xi.eadd(yj) if shifted_exp in prod: - prod[shifted_exp] += ai*bj + prod[shifted_exp] += ai * bj else: - prod[shifted_exp] = ai*bj + prod[shifted_exp] = ai * bj return tuple(prod.items()) ############### @@ -538,7 +542,7 @@ cdef tuple tup_mul(tuple p1, tuple p2): ############### cdef tuple monom_sortkey(ETuple exp): - """ + r""" Produce a sortkey for a monomial exponent w.r.t. degree reversed lexicographic ordering. """ @@ -549,7 +553,7 @@ cdef tuple monom_sortkey(ETuple exp): return (deg, rev) cpdef tuple poly_tup_sortkey(tuple eq_tup): - """ + r""" Return the sortkey of a polynomial represented as a tuple of ``(ETuple, coeff)`` pairs with respect to the degree lexicographical term order. @@ -576,7 +580,7 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) True - TESTS: + TESTS:: sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: R. = PolynomialRing(CyclotomicField(20)) @@ -591,7 +595,7 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): """ cdef ETuple exp cdef int i, l, nnz - cdef list key = list() + cdef list key = [] for exp, c in eq_tup: #Compare by term degree key.append(exp.unweighted_degree()) @@ -602,3 +606,4 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): key.append(-exp._data[2*i]) key.append(exp._data[2*i+1]) return tuple(key) + From d5e2b0b0d95e17d4d708523af511f2f9f8b2f2db Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sat, 10 Apr 2021 14:54:56 -0700 Subject: [PATCH 040/414] fix one doctest in f_matrix.py --- src/sage/combinat/root_system/f_matrix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index a14ed999c05..109ec44cafe 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1837,6 +1837,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): sage: f = FMatrix(FusionRing("A1",3)) # indirect doctest sage: f.find_orthogonal_solution() # long time + Computing F-symbols for The Fusion Ring of Type A1 and level 3 with Integer Ring coefficients with 71 variables... Set up 134 hex and orthogonality constraints... Partitioned 134 equations into 17 components of size: [12, 12, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1] From f90eb4c63b0921206bf32a7440e27dc013145df3 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 12 Apr 2021 23:03:22 +0200 Subject: [PATCH 041/414] manual cache for fast fusion ring cdef methods, removed long doctests --- src/sage/combinat/root_system/f_matrix.py | 5 +- .../fast_parallel_fmats_methods.pxd | 5 +- .../fast_parallel_fmats_methods.pyx | 13 +- .../fast_parallel_fusion_ring_braid_repn.pxd | 5 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 147 +++++------------- 5 files changed, 49 insertions(+), 126 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 109ec44cafe..876735e53d7 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -268,7 +268,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab True sage: f = FMatrix(FusionRing("G2",2)) - sage: f.find_orthogonal_solution(verbose=False) # long time (~18 s) + sage: f.find_orthogonal_solution(verbose=False) # long time (~11 s) sage: f.field() # long time Algebraic Field """ @@ -2335,7 +2335,7 @@ def certify_pentagons(self,use_mp=True,verbose=False): Partitioned 6 equations into 6 components of size: [1, 1, 1, 1, 1, 1] Computing appropriate NumberField... - sage: f.certify_pentagons() # long time + sage: f.certify_pentagons() # long time (~1.5s) Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients """ fvars_copy = deepcopy(self._fvars) @@ -2394,4 +2394,3 @@ def fvars_are_real(self): print("The F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) return False return True - diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index 861504a79a3..9c5370130c0 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -4,6 +4,5 @@ cpdef update_child_fmats(factory, tuple data_tup) cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) -cpdef executor(params) -cpdef collect_eqns(proc) - +cpdef executor(tuple params) +cpdef list collect_eqns(int proc) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index a66b1d0da97..a359c238fc8 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -443,7 +443,7 @@ cpdef update_child_fmats(factory, tuple data_tup): ### Reducers ### ################ -cpdef collect_eqns(proc): +cpdef list collect_eqns(int proc): r""" Helper function for returning processed results back to parent process. @@ -460,10 +460,10 @@ cpdef collect_eqns(proc): sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns sage: len(collect_eqns(0)) == 63 True - sage: fmats = FMatrix(FusionRing("G2",2)) + sage: fmats = FMatrix(FusionRing("C3",1)) sage: params = (('get_reduced_pentagons', id(fmats)), (0,1)) sage: executor(params) - sage: len(collect_eqns(0)) == 4911 + sage: len(collect_eqns(0)) == 374 True """ #Discard the zero polynomial @@ -486,7 +486,7 @@ cdef dict mappers = { "pent_verify": pent_verify } -cpdef executor(params): +cpdef executor(tuple params): r""" Execute a function defined in this module (``sage.combinat.root_system.fast_parallel_fmats_methods``) in a worker @@ -518,10 +518,10 @@ cpdef executor(params): sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns sage: len(collect_eqns(0)) == 63 True - sage: fmats = FMatrix(FusionRing("E8",2)) + sage: fmats = FMatrix(FusionRing("E6",1)) sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) sage: executor(params) - sage: len(collect_eqns(0)) == 11 + sage: len(collect_eqns(0)) == 6 True """ (fn_name, fmats_id), args = params @@ -572,4 +572,3 @@ cdef pent_verify(factory, tuple mp_params): feq_verif(factory,fvars,Nk_ij,id_anyon,nonuple) if i % 50000000 == 0 and i and verbose: print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) - diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd index a032a509637..9fcaddb56e0 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd @@ -1,4 +1,3 @@ cpdef _unflatten_entries(factory, list entries) -cpdef executor(params) -cpdef collect_results(proc) - +cpdef executor(tuple params) +cpdef list collect_results(int proc) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 4fca46e377f..436809c2518 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -23,7 +23,7 @@ worker_results = list() ### Mappers ### ############### -cpdef mid_sig_ij(fusion_ring,row,col,a,b): +cdef mid_sig_ij(fusion_ring,row,col,a,b): r""" Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two strands in the tree b -> xi # yi -> (a # a) # (a # a), which results in @@ -32,49 +32,6 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): .. WARNING:: This method assumes F-matrices are orthogonal. - - EXAMPLES:: - - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import mid_sig_ij - sage: FR = FusionRing("A1",4) # long time - sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) # long time - sage: one.weight() # long time - (1/2, -1/2) - sage: FR.get_computational_basis(one,two,4) # long time - [(two, two), (two, idd), (idd, two)] - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~7s) - sage: mid_sig_ij(FR, (two, two), (two, idd), one, two) # long time - 1/3*zeta48^10 - 2/3*zeta48^2 - - This method works for all possible types of fields returned by - ``self.fmats.field()``. - - TESTS:: - - sage: FR = FusionRing("A1",3) # long time - sage: FR.fusion_labels("a",inject_variables=True) # long time - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time - sage: _, _, to_opt = FR.fmats.field().optimized_representation() # long time - sage: a2**4 # long time - 2*a0 + 3*a2 - sage: FR.get_computational_basis(a2,a2,4) # long time - [(a2, a2), (a2, a0), (a0, a2)] - sage: to_opt(mid_sig_ij(FR,(a2, a0),(a2, a2),a2,a2)) # long time - -2024728666370660589/10956322398441542221*a1^30 - 34142146914395596291/21912644796883084442*a1^28 - - 21479437628091413631/21912644796883084442*a1^26 + 260131910217202103829/21912644796883084442*a1^24 - + 69575612911670713183/10956322398441542221*a1^22 + 25621808994337724689/1992058617898462222*a1^20 - - 1975139725303994650417/21912644796883084442*a1^18 - 1315664901396537703585/21912644796883084442*a1^16 - - 2421451803369354765026/10956322398441542221*a1^14 - 5963323855935165859057/21912644796883084442*a1^12 - - 4477124943233705460859/21912644796883084442*a1^10 - 2001454824483021618178/10956322398441542221*a1^8 - - 2120319455379289595185/21912644796883084442*a1^6 - 15722612944437234961/755608441271830498*a1^4 - - 39862668562651453480/10956322398441542221*a1^2 - 6967145776903524195/10956322398441542221 - sage: FR = FusionRing("G2",2) # long time - sage: FR.fusion_labels("g",inject_variables=True) # long time - sage: FR.get_computational_basis(g1,g2,4) # long time - [(g3, g2), (g3, g1), (g2, g3), (g2, g0), (g1, g3), (g1, g1), (g0, g2)] - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) - sage: mid_sig_ij(FR,(g2, g3),(g1, g1),g1,g2) # long time - -0.4566723195695565? + 0.0805236512828312?*I """ #Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars @@ -95,7 +52,7 @@ cpdef mid_sig_ij(fusion_ring,row,col,a,b): entry += f1 * f2 * r * f3 * f4 return entry -cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): +cdef odd_one_out_ij(fusion_ring,xi,xj,a,b): r""" Compute the `xi`, `xj` entry of the braid generator on the two right-most strands, corresponding to the tree b -> (xi # a) -> (a # a) # a, which @@ -104,49 +61,6 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): .. WARNING:: This method assumes F-matrices are orthogonal. - - EXAMPLES:: - - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import odd_one_out_ij - sage: FR = FusionRing("A1",4) # long time - sage: FR.fusion_labels(["one","two","three","four","five"],inject_variables=True) # long time - sage: FR.get_computational_basis(two,two,5) # long time - [(three, three, one), - (three, three, three), - (three, one, three), - (one, three, three), - (one, one, one)] - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time - sage: odd_one_out_ij(FR,one,three,two,two) # long time - 2/3*zeta48^12 - 1/3*zeta48^8 - 1/3*zeta48^4 - 1/3 - - This method works for all possible types of fields returned by - ``self.fmats.field()``. - - TESTS:: - - sage: FR = FusionRing("A1",3) # long time - sage: FR.fusion_labels("a",inject_variables=True) # long time - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time - sage: _, _, to_opt = FR.fmats.field().optimized_representation() # long time - sage: a2**3 # long time - a0 + 2*a2 - sage: FR.get_computational_basis(a2,a2,3) # long time - [(a2,), (a0,)] - sage: to_opt(odd_one_out_ij(FR,a0,a2,a2,a2)) # long time - 6341990144855406911/21912644796883084442*a1^30 + 47313529044493641571/21912644796883084442*a1^28 - - 6964289120109414595/10956322398441542221*a1^26 - 406719371329322780627/21912644796883084442*a1^24 - + 87598732372849355687/10956322398441542221*a1^22 - 456724726845194775/19723352652460022*a1^20 - + 3585892725441116840515/21912644796883084442*a1^18 - 645866255979227573282/10956322398441542221*a1^16 - + 7958479159087829772639/21912644796883084442*a1^14 + 789748976956837633826/10956322398441542221*a1^12 - + 3409710648897945752185/21912644796883084442*a1^10 + 903956381582048110980/10956322398441542221*a1^8 - + 192973084151342020307/21912644796883084442*a1^6 - 9233312083438019435/755608441271830498*a1^4 - + 667869266552877781/10956322398441542221*a1^2 + 17644302696056968099/21912644796883084442 - sage: FR = FusionRing("G2",2) # long time - sage: FR.fusion_labels("g",inject_variables=True) # long time - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) - sage: odd_one_out_ij(FR,g1,g2,g1,g1) # long time - -0.2636598866349343? + 0.4566723195695565?*I """ #Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars @@ -162,9 +76,23 @@ cpdef odd_one_out_ij(fusion_ring,xi,xj,a,b): entry += f1 * r * f2 return entry -#Cache methods -mid_sig_ij = cached_function(mid_sig_ij, name='mid_sig_ij') -odd_one_out_ij = cached_function(odd_one_out_ij, name='odd_one_out_ij') +#Cache methods (manually for cdef methods) +cdef odd_one_out_ij_cache = dict() +cdef mid_sig_ij_cache = dict() + +cdef cached_mid_sig_ij(fusion_ring,row,col,a,b): + if (row,col,a,b) in mid_sig_ij_cache: + return mid_sig_ij_cache[row,col,a,b] + entry = mid_sig_ij(fusion_ring,row,col,a,b) + mid_sig_ij_cache[row,col,a,b] = entry + return entry + +cdef cached_odd_one_out_ij(fusion_ring,xi,xj,a,b): + if (xi,xj,a,b) in odd_one_out_ij_cache: + return odd_one_out_ij_cache[xi,xj,a,b] + entry = odd_one_out_ij(fusion_ring,xi,xj,a,b) + odd_one_out_ij_cache[xi,xj,a,b] = entry + return entry @cython.nonecheck(False) @cython.cdivision(True) @@ -219,7 +147,7 @@ cdef sig_2k(fusion_ring, tuple args): #Handle the special case k = 1 if k == 1: - entry = mid_sig_ij(fusion_ring,m[:2],(f,e),a,root) + entry = cached_mid_sig_ij(fusion_ring,m[:2],(f,e),a,root) #Avoid pickling cyclotomic field element objects if must_flatten_coeff: @@ -233,7 +161,7 @@ cdef sig_2k(fusion_ring, tuple args): for p in fusion_ring.basis(): f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[k-1],m[k],root,l[k-2],p) f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,e,root,q,p) - entry += f1 * mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 + entry += f1 * cached_mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 #Avoid pickling cyclotomic field element objects if must_flatten_coeff: @@ -285,7 +213,7 @@ cdef odd_one_out(fusion_ring, tuple args): #Handle a couple of small special cases if n_strands == 3: - entry = odd_one_out_ij(fusion_ring,m[-1],f,a,b) + entry = cached_odd_one_out_ij(fusion_ring,m[-1],f,a,b) #Avoid pickling cyclotomic field element objects if must_flatten_coeff: @@ -303,7 +231,7 @@ cdef odd_one_out(fusion_ring, tuple args): for p in fusion_ring.basis(): f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[-1],a,root,l[-1],p) f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,a,root,q,p) - entry += f1 * odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 + entry += f1 * cached_odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 #Avoid pickling cyclotomic field element objects if must_flatten_coeff: @@ -315,7 +243,7 @@ cdef odd_one_out(fusion_ring, tuple args): ### Reducers ### ################ -cpdef collect_results(proc): +cpdef list collect_results(int proc): """ Helper function for returning processed results back to parent process. @@ -326,13 +254,13 @@ cpdef collect_results(proc): EXAMPLES:: sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor - sage: FR = FusionRing("A1",4) # long time - sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) # long time - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time - sage: params = (('sig_2k',id(FR)),(0,1,(2,one,one,9))) # long time - sage: executor(params) # long time + sage: FR = FusionRing("A2",1) + sage: FR.fusion_labels("a",inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: params = (('sig_2k',id(FR)),(0,1,(2,a2,a0,9))) + sage: executor(params) sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results - sage: len(collect_results(0)) == 171 # long time + sage: len(collect_results(0)) == 1 True """ #Discard the zero polynomial @@ -351,7 +279,7 @@ cdef dict mappers = { "odd_one_out": odd_one_out } -cpdef executor(params): +cpdef executor(tuple params): r""" Execute a function registered in this module's ``mappers`` in a worker process, and supply the ``FusionRing`` parameter by @@ -378,12 +306,12 @@ cpdef executor(params): sage: len(collect_results(0)) == 13 # long time True sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor, collect_results - sage: FR = FusionRing("B2",2) # long time - sage: FR.fusion_labels(['I0','Y1','X','Z','Xp','Y2'],inject_variables=True) # long time - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time (~23 s) - sage: params = (('odd_one_out',id(FR)),(0,1,(X,Xp,5))) # long time - sage: executor(params) # long time - sage: len(collect_results(0)) == 54 # long time + sage: FR = FusionRing("A1",2) + sage: FR.fusion_labels("a",inject_variables=True) + sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: params = (('odd_one_out',id(FR)),(0,1,(a2,a2,5))) + sage: executor(params) + sage: len(collect_results(0)) == 1 True """ (fn_name, fr_id), args = params @@ -420,4 +348,3 @@ cpdef _unflatten_entries(fusion_ring, list entries): if F != QQbar: for i, (coord, entry) in enumerate(entries): entries[i] = (coord, F(entry)) - From 9109bbf28c2aab26a10c8fd1a61b6c967d955bf3 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Mon, 12 Apr 2021 14:59:40 -0700 Subject: [PATCH 042/414] give more information in total_q_order docstring --- src/sage/combinat/root_system/fusion_ring.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 53a5a1438c2..25ff5433ccf 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -981,7 +981,8 @@ def global_q_dimension(self, base_coercion=True): def total_q_order(self, base_coercion=True): r""" Return the positive square root of ``self.global_q_dimension()`` - as an element of ``self.field()``. + as an element of ``self.field()``. Implemented as `D_{+}e^{-i\pi c/4}` + where `D_+` is ``self.D_plus()`` and `c` is ``self.virasoro_central_charge()`` EXAMPLES:: From 14f7b5cb0e56048aef98357f45953e91e8a9a1c6 Mon Sep 17 00:00:00 2001 From: Daniel Bump Date: Thu, 15 Apr 2021 13:53:17 -0700 Subject: [PATCH 043/414] initialize self._kp in f_matrix --- .../reference/combinat/media/fusiontree.png | Bin 37425 -> 37424 bytes src/sage/combinat/root_system/f_matrix.py | 1 + 2 files changed, 1 insertion(+) diff --git a/src/doc/en/reference/combinat/media/fusiontree.png b/src/doc/en/reference/combinat/media/fusiontree.png index 1fde2a94c81f02f7bd8d9752d5b46c1469436d48..ecad6ab87eae6f8f1870de6963e6cb9db617560f 100644 GIT binary patch delta 15 WcmdnEglWSPCYH_sKlhESI+Fk}a0QeA delta 16 Xcmdn6glXdvCf3dXKX=}ZEIN|_G}Q&6 diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 876735e53d7..ec849e1bafd 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -298,6 +298,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.ideal_basis = list() self._solved = set() self._ks = dict() + self._kp = dict() self._nnz = self._get_known_nonz() self._chkpt_status = -1 From eb488914e9c893c754bea88e7c34da786ad0627e Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Thu, 15 Apr 2021 14:40:30 -0700 Subject: [PATCH 044/414] multiprocessing not the default after this commit. Doctests pass on OSX and Ubuntu, but run slowly --- src/sage/combinat/root_system/f_matrix.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index ec849e1bafd..39dbdd3c51f 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1294,8 +1294,8 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1)])) 11 sage: from multiprocessing import Pool - sage: pool = Pool() - sage: mp_params = [(i,pool._processes) for i in range(pool._processes)] + sage: pool = None + sage: mp_params = [(0,1)] sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=pool,chunksize=1,mp_thresh=0)) 33 """ @@ -1457,7 +1457,7 @@ def _tup_to_fpoly(self,eq_tup): ....: set_start_method('fork') ....: except: ....: pass - sage: pool = Pool() + sage: pool = None sage: he = f.get_defining_equations('hexagons',pool) sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True @@ -1477,7 +1477,7 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat ....: set_start_method('fork') ....: except: ....: pass - sage: pool = Pool() + sage: pool = None sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey @@ -1688,18 +1688,19 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose ....: set_start_method('fork') # context can be set only once ....: except RuntimeError: ....: pass - sage: pool = Pool() + sage: pool = None sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: gb = f._par_graph_gb(worker_pool=pool) Partitioned 10 equations into 2 components of size: [4, 1] sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs - sage: [f._tup_to_fpoly(_unflatten_coeffs(f.field(), t)) for t in gb] - [fx0 - 1, + sage: ret = [f._tup_to_fpoly(_unflatten_coeffs(f.field(), t)) for t in gb] + sage: ret.sort(); ret + [fx4 + (-zeta80^24 + zeta80^16), fx2 - fx3, - fx3^2 + (zeta80^24 - zeta80^16), - fx4 + (-zeta80^24 + zeta80^16), - fx1 + (zeta80^24 - zeta80^16)] + fx1 + (zeta80^24 - zeta80^16), + fx0 - 1, + fx3^2 + (zeta80^24 - zeta80^16)] """ if eqns is None: eqns = self.ideal_basis small_comps = list() @@ -1940,7 +1941,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() - def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start="", use_mp=True, verbose=True): + def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start="", use_mp=False, verbose=True): r""" Solve the the hexagon and pentagon relations, along with orthogonality constraints, to evaluate an orthogonal F-matrix. From 4a57f9652e109d551f45d38a9d88c621d703a771 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 16 Apr 2021 13:31:39 -0400 Subject: [PATCH 045/414] no default multiprocessing for braid representations --- src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx | 4 ++-- src/sage/combinat/root_system/fusion_ring.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index a359c238fc8..5b08eb2b5e6 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -349,7 +349,7 @@ cpdef compute_gb(factory, tuple args): ....: set_start_method('fork') # context can be set only once ....: except RuntimeError: ....: pass - sage: f.get_defining_equations('hexagons',worker_pool=Pool(),output=False) # long time + sage: f.get_defining_equations('hexagons',worker_pool=None,output=False) # long time sage: partition = f._partition_eqns() # long time Partitioned 261 equations into 57 components of size: [24, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, @@ -421,7 +421,7 @@ cpdef update_child_fmats(factory, tuple data_tup): ....: set_start_method('fork') # context can be set only once ....: except RuntimeError: ....: pass - sage: pool = Pool() + sage: pool = None sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 25ff5433ccf..218989cbfae 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -1184,7 +1184,7 @@ def _emap(self, mapper, input_args, worker_pool=None): a single process. If ``worker_pool`` is provided, the function attempts to determine whether it should use multiprocessing based on the length of the input iterable. If it cannot determine - the length of the input iterable then it uses multiprocessing + the length of the input iterable then it uses multiprocessing with the default chunksize of `1` if chunksize is not explicitly provided. @@ -1227,7 +1227,7 @@ def get_braid_generators(self, checkpoint=False, save_results="", warm_start="", - use_mp=True, + use_mp=False, verbose=True): r""" Compute generators of the Artin braid group on ``n_strands`` strands. From 6e392b07ba660e2f0651fc5e86dd01975575bf4f Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 21 Apr 2021 17:36:46 -0400 Subject: [PATCH 046/414] fixed assumed one-to-one collect_eqns to process map bug, made use_mp default again --- src/sage/combinat/root_system/f_matrix.py | 39 ++-- .../fast_parallel_fmats_methods.pxd | 6 - .../fast_parallel_fmats_methods.pyx | 195 +++++------------- .../fast_parallel_fusion_ring_braid_repn.pxd | 1 - .../fast_parallel_fusion_ring_braid_repn.pyx | 50 +---- src/sage/combinat/root_system/fusion_ring.py | 20 +- 6 files changed, 89 insertions(+), 222 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 39dbdd3c51f..ac262a0b644 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -20,13 +20,13 @@ from copy import deepcopy from itertools import product, zip_longest -from multiprocessing import cpu_count, Pool, set_start_method +from multiprocessing import cpu_count, Pool, Queue, set_start_method import numpy as np import os from sage.combinat.root_system.fast_parallel_fmats_methods import ( _backward_subs, _solve_for_linear_terms, - collect_eqns, executor + executor ) from sage.combinat.root_system.poly_tup_engine import ( apply_coeff_map, constant_coeff, @@ -1294,8 +1294,8 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1)])) 11 sage: from multiprocessing import Pool - sage: pool = None - sage: mp_params = [(0,1)] + sage: pool = Pool() + sage: mp_params = [(i,pool._processes) for i in range(pool._processes)] sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=pool,chunksize=1,mp_thresh=0)) 33 """ @@ -1310,21 +1310,18 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t if chunksize is None: chunksize = n // (worker_pool._processes**2) + 1 no_mp = worker_pool is None or n < mp_thresh - #Map phase. Casting Async Object blocks execution... Each process holds results - #in its copy of fmats.temp_eqns + #Map phase input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) if no_mp: - list(map(executor,input_iter)) + mapped = map(executor,input_iter) else: - list(worker_pool.imap_unordered(executor,input_iter,chunksize=chunksize)) + mapped = worker_pool.imap_unordered(executor,input_iter,chunksize=chunksize) #Reduce phase - if no_mp: - results = collect_eqns(0) - else: - results = set() - for child_eqns in worker_pool.imap_unordered(collect_eqns,range(worker_pool._processes)): + results = set() + for child_eqns in mapped: + if child_eqns is not None: results.update(child_eqns) - results = list(results) + results = list(results) return results ######################## @@ -1457,7 +1454,7 @@ def _tup_to_fpoly(self,eq_tup): ....: set_start_method('fork') ....: except: ....: pass - sage: pool = None + sage: pool = Pool() sage: he = f.get_defining_equations('hexagons',pool) sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True @@ -1477,7 +1474,7 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat ....: set_start_method('fork') ....: except: ....: pass - sage: pool = None + sage: pool = Pool() sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey @@ -1536,6 +1533,8 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): #Compute new reduction params, send to child processes if any, and update eqns self._update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) + n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) + eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool) eqns.sort(key=poly_tup_sortkey) if verbose: @@ -1688,7 +1687,7 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose ....: set_start_method('fork') # context can be set only once ....: except RuntimeError: ....: pass - sage: pool = None + sage: pool = Pool() sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: gb = f._par_graph_gb(worker_pool=pool) Partitioned 10 equations into 2 components of size: @@ -1861,7 +1860,6 @@ def _get_explicit_solution(self,eqns=None,verbose=True): eqns = self.ideal_basis #Don't add square fixers when warm starting from a late-stage checkpoint if self._chkpt_status < 5: - # self._add_square_fixers() n = self._poly_ring.ngens() one = self._field.one() for fx, rhs in self._ks.items(): @@ -1894,7 +1892,6 @@ def _get_explicit_solution(self,eqns=None,verbose=True): #Otherwise, compute the component variety and select a point to obtain a numerical solution else: sols = self._get_component_variety(comp,part) - # assert len(sols) > 1, "No real solution exists... component with variables {} has no real points".format(comp) for fx, rhs in sols[0].items(): non_cyclotomic_roots.append((fx,rhs)) must_change_base_field = True @@ -1941,7 +1938,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() - def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start="", use_mp=False, verbose=True): + def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start="", use_mp=True, verbose=True): r""" Solve the the hexagon and pentagon relations, along with orthogonality constraints, to evaluate an orthogonal F-matrix. @@ -2343,7 +2340,7 @@ def certify_pentagons(self,use_mp=True,verbose=False): fvars_copy = deepcopy(self._fvars) self._fvars = {sextuple: float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} if use_mp: - pool = Pool(processes=cpu_count()) + pool = Pool() else: pool = None n_proc = pool._processes if pool is not None else 1 diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index 9c5370130c0..f54fa2ff174 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -1,8 +1,2 @@ -cpdef update_reduce(factory, tuple eq_tup) -cpdef compute_gb(factory, tuple args) -cpdef update_child_fmats(factory, tuple data_tup) - cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) - cpdef executor(tuple params) -cpdef list collect_eqns(int proc) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 5b08eb2b5e6..c9c09294c4c 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -141,6 +141,30 @@ cpdef _backward_subs(factory): kp = compute_known_powers(get_variables_degrees([rhs]), d, one) factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), factory._ks).items()) +cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): + """ + Cython version of fmat class method. Using cdef for fastest dispatch + """ + if _Nk_ij(a,b,x) == 0 or _Nk_ij(x,c,d) == 0 or _Nk_ij(b,c,y) == 0 or _Nk_ij(a,y,d) == 0: + return 0 + #Some known F-symbols + if a == id_anyon: + if x == b and y == d: + return 1 + else: + return 0 + if b == id_anyon: + if x == a and y == c: + return 1 + else: + return 0 + if c == id_anyon: + if x == d and y == b: + return 1 + else: + return 0 + return fvars[a,b,c,d,x,y] + ###################################### ### Fast fusion coefficients cache ### ###################################### @@ -169,33 +193,6 @@ cpdef _backward_subs(factory): ### Mappers ### ############### -#Define a global temporary worker results repository -cdef list worker_results = list() - -cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): - """ - Cython version of fmat class method. Using cdef for fastest dispatch - """ - if _Nk_ij(a,b,x) == 0 or _Nk_ij(x,c,d) == 0 or _Nk_ij(b,c,y) == 0 or _Nk_ij(a,y,d) == 0: - return 0 - #Some known F-symbols - if a == id_anyon: - if x == b and y == d: - return 1 - else: - return 0 - if b == id_anyon: - if x == a and y == c: - return 1 - else: - return 0 - if c == id_anyon: - if x == d and y == b: - return 1 - else: - return 0 - return fvars[a,b,c,d,x,y] - cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): """ Given an FMatrix factory and a sextuple, return a hexagon equation @@ -218,7 +215,7 @@ cdef get_reduced_hexagons(factory, tuple mp_params): Set up and reduce the hexagon equations corresponding to this worker. """ #Set up multiprocessing parameters - global worker_results + cdef list worker_results = list() cdef int child_id, n_proc cdef unsigned long i child_id, n_proc = mp_params @@ -246,6 +243,8 @@ cdef get_reduced_hexagons(factory, tuple mp_params): worker_results.append(red) + return collect_eqns(worker_results) + cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, id_anyon, zero, tuple nonuple, bint prune=False): r""" Given an FMatrix factory and a nonuple, return a pentagon equation @@ -268,7 +267,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): Set up and reduce the pentagon equations corresponding to this worker. """ #Set up multiprocessing parameters - global worker_results + cdef list worker_results = list() cdef int child_id, n_proc child_id, n_proc = mp_params cdef unsigned long i @@ -298,80 +297,36 @@ cdef get_reduced_pentagons(factory, tuple mp_params): red = _flatten_coeffs(red) worker_results.append(red) + return collect_eqns(worker_results) -cpdef update_reduce(factory, tuple eq_tup): +cdef list update_reduce(factory, list eqns): r""" Substitute known values, known squares, and reduce. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("B4",1), fusion_label="b", inject_variables=True) - creating variables fx1..fx14 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - sage: p = 2*fx0**2 - 3/7*fx0*fx1*fx3**3 + 5*fx0*fx4 - sage: from sage.combinat.root_system.poly_tup_engine import * - sage: f.ideal_basis = [poly_to_tup(p)] - sage: f._ks = {3: 2} - sage: f._var_degs = get_variables_degrees(f.ideal_basis) - sage: known_vals = {4: poly_to_tup(fx11**2)} - sage: f._nnz - (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 0, 0, 0, 0) - sage: f._kp = compute_known_powers(f._var_degs,known_vals,f._field.one()) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import update_reduce, collect_eqns - sage: update_reduce(f, f.ideal_basis[0]) - sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs - sage: [f._tup_to_fpoly(_unflatten_coeffs(f._field,p)) for p in collect_eqns(0)] - [fx1*fx3 - 35/6*fx11^2 - 7/3*fx0] """ - global worker_results + cdef list res = list() cdef NumberFieldElement_absolute one = factory._field.one() + cdef tuple eq_tup, red, unflat + cdef dict eq_dict - #Construct cyclotomic field elts from list repn - cdef tuple unflat = _unflatten_coeffs(factory._field, eq_tup) + for i in range(len(eqns)): + eq_tup = eqns[i] + #Construct cyclotomic field elts from list repn + unflat = _unflatten_coeffs(factory._field, eq_tup) - cdef dict eq_dict = subs(unflat,factory._kp,one) - cdef tuple red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks,one) + eq_dict = subs(unflat,factory._kp,one) + red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks,one) - #Avoid pickling cyclotomic coefficients - red = _flatten_coeffs(red) + #Avoid pickling cyclotomic coefficients + red = _flatten_coeffs(red) - worker_results.append(red) + res.append(red) + return collect_eqns(res) -cpdef compute_gb(factory, tuple args): +cdef list compute_gb(factory, tuple args): r""" Compute the reduced Groebner basis for given equations iterable. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("A2",2)) - sage: from multiprocessing import Pool, set_start_method - sage: try: - ....: set_start_method('fork') # context can be set only once - ....: except RuntimeError: - ....: pass - sage: f.get_defining_equations('hexagons',worker_pool=None,output=False) # long time - sage: partition = f._partition_eqns() # long time - Partitioned 261 equations into 57 components of size: - [24, 12, 12, 12, 12, 12, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 1, 1, 1, 1, 1] - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import compute_gb, collect_eqns - sage: args = (partition[(29, 40, 83, 111, 148, 154)], "degrevlex") # long time - sage: compute_gb(f, args) # long time - sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs - sage: [f._tup_to_fpoly(_unflatten_coeffs(f._field,t)) for t in collect_eqns(0)] # long time - [fx83*fx154 - fx111, - fx29^2*fx154 + fx29, - fx29*fx148 - fx40, - fx29*fx154^2 + fx154, - fx83*fx148 + fx29*fx154, - fx40*fx83 - fx29, - fx111*fx148 - fx154, - fx40*fx154 + fx148, - fx40*fx111 - fx29*fx154, - fx29*fx111 + fx83] """ - global worker_results + cdef list res = list() cdef list eqns, sorted_vars eqns, term_order = args #Define smaller poly ring in component vars @@ -404,34 +359,14 @@ cpdef compute_gb(factory, tuple args): #Avoid pickling cyclotomic coefficients t = _flatten_coeffs(t) - worker_results.append(t) + res.append(t) + return collect_eqns(res) -cpdef update_child_fmats(factory, tuple data_tup): +cdef update_child_fmats(factory, tuple data_tup): r""" One-to-all communication used to update FMatrix object after each triangular elim step. We must update the algorithm's state values. These are: ``_fvars``, ``_solved``, ``_ks``, ``_var_degs``, ``_nnz``, and ``_kp``. - - TESTS:: - - sage: f = FMatrix(FusionRing("A1",3)) - sage: f.get_orthogonality_constraints(output=False) - sage: from multiprocessing import Pool, set_start_method - sage: try: - ....: set_start_method('fork') # context can be set only once - ....: except RuntimeError: - ....: pass - sage: pool = None - sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) - sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey - sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: f.mp_thresh = 0 - sage: f._triangular_elim(worker_pool=pool) # indirect doctest - Elimination epoch completed... 10 eqns remain in ideal basis - Elimination epoch completed... 0 eqns remain in ideal basis - sage: f.ideal_basis - [] """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object @@ -443,33 +378,16 @@ cpdef update_child_fmats(factory, tuple data_tup): ### Reducers ### ################ -cpdef list collect_eqns(int proc): +cdef inline list collect_eqns(list eqns): r""" Helper function for returning processed results back to parent process. Trivial reducer: simply collects objects with the same key in the worker. This method is only useful when called after :meth:`executor`, whose function argument appends output to the ``worker_results`` list. - - EXAMPLES:: - - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor - sage: fmats = FMatrix(FusionRing("A1",3)) - sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) - sage: executor(params) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns - sage: len(collect_eqns(0)) == 63 - True - sage: fmats = FMatrix(FusionRing("C3",1)) - sage: params = (('get_reduced_pentagons', id(fmats)), (0,1)) - sage: executor(params) - sage: len(collect_eqns(0)) == 374 - True """ #Discard the zero polynomial - global worker_results - reduced = set(worker_results) - set([tuple()]) - worker_results = [] + reduced = set(eqns) - set([tuple()]) return list(reduced) ############################## @@ -514,14 +432,11 @@ cpdef executor(tuple params): sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor sage: fmats = FMatrix(FusionRing("A1",3)) sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) - sage: executor(params) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import collect_eqns - sage: len(collect_eqns(0)) == 63 + sage: len(executor(params)) == 63 True sage: fmats = FMatrix(FusionRing("E6",1)) sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) - sage: executor(params) - sage: len(collect_eqns(0)) == 6 + sage: len(executor(params)) == 6 True """ (fn_name, fmats_id), args = params @@ -534,11 +449,10 @@ cpdef executor(tuple params): ### Verification ### #################### -cdef feq_verif(factory, fvars, Nk_ij, id_anyon, tuple nonuple, float tol=5e-8): +cdef feq_verif(factory, worker_results, fvars, Nk_ij, id_anyon, tuple nonuple, float tol=5e-8): r""" Check the pentagon equation corresponding to the given nonuple. """ - global worker_results a, b, c, d, e, f, g, k, l = nonuple cdef float diff, lhs, rhs @@ -562,6 +476,7 @@ cdef pent_verify(factory, tuple mp_params): cdef float t0 cdef tuple nonuple cdef unsigned long long i + cdef list worker_results = list() #Pre-compute common parameters for speed Nk_ij = factory._FR.Nk_ij @@ -569,6 +484,6 @@ cdef pent_verify(factory, tuple mp_params): id_anyon = factory._FR.one() for i, nonuple in enumerate(product(factory._FR.basis(), repeat=9)): if i % n_proc == child_id: - feq_verif(factory,fvars,Nk_ij,id_anyon,nonuple) + feq_verif(factory,worker_results,fvars,Nk_ij,id_anyon,nonuple) if i % 50000000 == 0 and i and verbose: print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd index 9fcaddb56e0..a992f0339a4 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd @@ -1,3 +1,2 @@ cpdef _unflatten_entries(factory, list entries) cpdef executor(tuple params) -cpdef list collect_results(int proc) diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 436809c2518..9be0bf567fe 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -16,9 +16,6 @@ from itertools import product from sage.misc.cachefunc import cached_function from sage.rings.qqbar import QQbar -#Define a global temporary worker results repository -worker_results = list() - ############### ### Mappers ### ############### @@ -109,7 +106,7 @@ cdef sig_2k(fusion_ring, tuple args): child_id, n_proc, fn_args = args k, a, b, n_strands = fn_args cdef int ctr = -1 - global worker_results + cdef list worker_results = list() #Get computational basis cdef list comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) cdef dict basis_dict = { elt : i for i, elt in enumerate(comp_basis) } @@ -168,6 +165,7 @@ cdef sig_2k(fusion_ring, tuple args): entry = entry.list() worker_results.append(((basis_dict[nnz_pos], i), entry)) + return worker_results @cython.nonecheck(False) @cython.cdivision(True) @@ -181,7 +179,7 @@ cdef odd_one_out(fusion_ring, tuple args): _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() - global worker_results + cdef list worker_results = list() cdef int child_id, n_proc child_id, n_proc, fn_args = args a, b, n_strands = fn_args @@ -238,36 +236,7 @@ cdef odd_one_out(fusion_ring, tuple args): entry = entry.list() worker_results.append(((basis_dict[nnz_pos],i), entry)) - -################ -### Reducers ### -################ - -cpdef list collect_results(int proc): - """ - Helper function for returning processed results back to parent process. - - Trivial reducer: simply collects objects with the same key in the worker. - This method is only useful when called after :meth:`executor`, whose - function argument appends output to the ``worker_results`` list. - - EXAMPLES:: - - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor - sage: FR = FusionRing("A2",1) - sage: FR.fusion_labels("a",inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: params = (('sig_2k',id(FR)),(0,1,(2,a2,a0,9))) - sage: executor(params) - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results - sage: len(collect_results(0)) == 1 - True - """ - #Discard the zero polynomial - global worker_results - reduced = worker_results - worker_results = [] - return reduced + return worker_results ############################## ### Parallel code executor ### @@ -301,24 +270,21 @@ cpdef executor(tuple params): sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) # long time - sage: executor(params) # long time - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import collect_results - sage: len(collect_results(0)) == 13 # long time + sage: len(executor(params)) == 13 # long time True - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor, collect_results + sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor sage: FR = FusionRing("A1",2) sage: FR.fusion_labels("a",inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) sage: params = (('odd_one_out',id(FR)),(0,1,(a2,a2,5))) - sage: executor(params) - sage: len(collect_results(0)) == 1 + sage: len(executor(params)) == 1 True """ (fn_name, fr_id), args = params #Construct a reference to global FMatrix object in this worker's memory fusion_ring_obj = ctypes.cast(fr_id, ctypes.py_object).value #Bind module method to FMatrix object in worker process, and call the method - mappers[fn_name](fusion_ring_obj,args) + return mappers[fn_name](fusion_ring_obj,args) ###################################### ### Pickling circumvention helpers ### diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 218989cbfae..b602904d1aa 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -15,7 +15,7 @@ from multiprocessing import Pool, set_start_method from sage.combinat.q_analogues import q_int from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import ( - collect_results, executor, + executor, _unflatten_entries ) from sage.combinat.root_system.weyl_characters import WeylCharacterRing @@ -1204,20 +1204,16 @@ def _emap(self, mapper, input_args, worker_pool=None): n_proc = worker_pool._processes if worker_pool is not None else 1 input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] no_mp = worker_pool is None - #Map phase. Casting Async Object blocks execution... Each process holds results - #in its copy of fmats.temp_eqns + #Map phase input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) + results = list() if no_mp: - list(map(executor,input_iter)) + mapped = map(executor,input_iter) else: - list(worker_pool.imap_unordered(executor,input_iter,chunksize=1)) + mapped = worker_pool.imap_unordered(executor,input_iter,chunksize=1) #Reduce phase - if no_mp: - results = collect_results(0) - else: - results = [] - for worker_results in worker_pool.imap_unordered(collect_results,range(worker_pool._processes),chunksize=1): - results.extend(worker_results) + for worker_results in mapped: + results.extend(worker_results) return results def get_braid_generators(self, @@ -1227,7 +1223,7 @@ def get_braid_generators(self, checkpoint=False, save_results="", warm_start="", - use_mp=False, + use_mp=True, verbose=True): r""" Compute generators of the Artin braid group on ``n_strands`` strands. From 6c6b76b52498e012feb2de7e6e0e609a7f0d244b Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 21 Apr 2021 17:53:06 -0400 Subject: [PATCH 047/414] update_child_fmats cannot be cdef --- .../fast_parallel_fmats_methods.pxd | 1 + .../fast_parallel_fmats_methods.pyx | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index f54fa2ff174..faed5a56f5e 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -1,2 +1,3 @@ +cpdef update_child_fmats(factory, tuple data_tup) cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) cpdef executor(tuple params) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index c9c09294c4c..21033456c1a 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -362,11 +362,32 @@ cdef list compute_gb(factory, tuple args): res.append(t) return collect_eqns(res) -cdef update_child_fmats(factory, tuple data_tup): +cpdef update_child_fmats(factory, tuple data_tup): r""" One-to-all communication used to update FMatrix object after each triangular elim step. We must update the algorithm's state values. These are: ``_fvars``, ``_solved``, ``_ks``, ``_var_degs``, ``_nnz``, and ``_kp``. + + TESTS:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: f.get_orthogonality_constraints(output=False) + sage: from multiprocessing import Pool, set_start_method + sage: try: + ....: set_start_method('fork') # context can be set only once + ....: except RuntimeError: + ....: pass + sage: pool = Pool() + sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) + sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f.mp_thresh = 0 + sage: f._triangular_elim(worker_pool=pool) # indirect doctest + Elimination epoch completed... 10 eqns remain in ideal basis + Elimination epoch completed... 0 eqns remain in ideal basis + sage: f.ideal_basis + [] """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object From 3eb09def29915f8630ebe37c8ed39197863fd480 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 21 Apr 2021 18:59:38 -0400 Subject: [PATCH 048/414] quick sleep fix for _update_child_fmats bug (same process takes two tasks off the queue) --- .../combinat/root_system/fast_parallel_fmats_methods.pyx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 21033456c1a..2e8145fb03c 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -27,6 +27,8 @@ from itertools import product from sage.rings.ideal import Ideal from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from time import sleep + ########################## ### Fast class methods ### ########################## @@ -395,6 +397,9 @@ cpdef update_child_fmats(factory, tuple data_tup): factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) + #Wait this process isn't used again + sleep(0.4) + ################ ### Reducers ### ################ From 9a1949ef38743d6c2218f9aefffa88cc5723aafe Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 22 Apr 2021 14:20:45 -0400 Subject: [PATCH 049/414] get_variables_degrees returns dictionary and factory._ks uses flattened coefficients --- src/sage/combinat/root_system/f_matrix.py | 11 ++--- .../fast_parallel_fmats_methods.pyx | 43 +++++++++++++------ .../fast_parallel_fusion_ring_braid_repn.pyx | 1 - .../combinat/root_system/poly_tup_engine.pxd | 9 ++-- .../combinat/root_system/poly_tup_engine.pyx | 26 ++++++----- 5 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index ac262a0b644..231e36c0c0b 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -20,7 +20,7 @@ from copy import deepcopy from itertools import product, zip_longest -from multiprocessing import cpu_count, Pool, Queue, set_start_method +from multiprocessing import cpu_count, Pool, set_start_method import numpy as np import os @@ -941,7 +941,7 @@ def _get_known_sq(self,eqns=None): F = self._field for eq_tup in eqns: if tup_fixes_sq(eq_tup): - ks[variables(eq_tup)[0]] = -F(list(eq_tup[-1][1])) + ks[variables(eq_tup)[0]] = tuple(-v for v in eq_tup[-1][1]) return ks def _get_known_nonz(self): @@ -1486,9 +1486,10 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat """ if eqns is None: eqns = self.ideal_basis - self._ks, self._var_degs = self._get_known_sq(eqns), get_variables_degrees(eqns) + self._ks = self._get_known_sq(eqns) + self._var_degs = ETuple(get_variables_degrees(eqns),self._poly_ring.ngens()) self._nnz = self._get_known_nonz() - self._kp = compute_known_powers(self._var_degs,self._get_known_vals(), self._field.one()) + self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) if worker_pool is not None and children_need_update: #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes @@ -1865,7 +1866,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): for fx, rhs in self._ks.items(): if fx not in self._solved: lt = (ETuple({fx : 2},n), one) - eqns.append((lt, (ETuple({},n), -rhs))) + eqns.append((lt, (ETuple({},n), -self._field(list(rhs))))) eqns_partition = self._partition_eqns(verbose=verbose) F = self._field diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 2e8145fb03c..a81599c8e85 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -134,14 +134,18 @@ cpdef _backward_subs(factory): 1 """ one = factory._field.one() - for var in reversed(factory._poly_ring.gens()): + _ks = {k : factory._FR.field()(list(v)) for k, v in factory._ks.items()} + vars = factory._poly_ring.gens() + n = len(vars) + for var in reversed(vars): sextuple = factory._var_to_sextuple[var] rhs = factory._fvars[sextuple] d = {var_idx: factory._fvars[factory._idx_to_sextuple[var_idx]] for var_idx in variables(rhs) if var_idx in factory._solved} if d: - kp = compute_known_powers(get_variables_degrees([rhs]), d, one) - factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), factory._ks).items()) + degs = ETuple(get_variables_degrees([rhs]),n) + kp = compute_known_powers(degs, d, one) + factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), _ks).items()) cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): """ @@ -229,9 +233,11 @@ cdef get_reduced_hexagons(factory, tuple mp_params): r_matrix = factory._FR.r_matrix _Nk_ij = factory._FR.Nk_ij id_anyon = factory._FR.one() - cdef NumberFieldElement_absolute one = factory._field.one() + _field = factory._field + cdef NumberFieldElement_absolute one = _field.one() cdef ETuple _nnz = factory._nnz - cdef dict _ks = factory._ks + # cdef dict _ks = factory._ks + _ks = {k : _field(list(v)) for k, v in factory._ks.items()} #Computation loop for i, sextuple in enumerate(product(basis, repeat=6)): @@ -281,9 +287,11 @@ cdef get_reduced_pentagons(factory, tuple mp_params): cdef dict fvars = factory._fvars _Nk_ij = factory._FR.Nk_ij id_anyon = factory._FR.one() - cdef NumberFieldElement_absolute one = factory._field.one() + _field = factory._field + cdef NumberFieldElement_absolute one = _field.one() cdef ETuple _nnz = factory._nnz - cdef dict _ks = factory._ks + # cdef dict _ks = factory._ks + _ks = {k : _field(list(v)) for k, v in factory._ks.items()} cdef MPolynomial_libsingular zero = factory._poly_ring.zero() #Computation loop @@ -306,17 +314,23 @@ cdef list update_reduce(factory, list eqns): Substitute known values, known squares, and reduce. """ cdef list res = list() - cdef NumberFieldElement_absolute one = factory._field.one() cdef tuple eq_tup, red, unflat cdef dict eq_dict + #Pre-compute common parameters for speed + _field = factory._field + one = _field.one() + _ks = {k : _field(list(v)) for k, v in factory._ks.items()} + cdef dict _kp = factory._kp + cdef ETuple _nnz = factory._nnz + for i in range(len(eqns)): eq_tup = eqns[i] #Construct cyclotomic field elts from list repn - unflat = _unflatten_coeffs(factory._field, eq_tup) + unflat = _unflatten_coeffs(_field, eq_tup) - eq_dict = subs(unflat,factory._kp,one) - red = reduce_poly_dict(eq_dict,factory._nnz,factory._ks,one) + eq_dict = subs(unflat,_kp,one) + red = reduce_poly_dict(eq_dict,_nnz,_ks,one) #Avoid pickling cyclotomic coefficients red = _flatten_coeffs(red) @@ -394,11 +408,12 @@ cpdef update_child_fmats(factory, tuple data_tup): #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object factory._fvars, factory._solved, factory._ks, factory._var_degs = data_tup + # factory._fvars, factory._solved, factory._var_degs = data_tup factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) #Wait this process isn't used again - sleep(0.4) + sleep(0.25) ################ ### Reducers ### @@ -420,6 +435,10 @@ cdef inline list collect_eqns(list eqns): ### Parallel code executor ### ############################## +def init(fmats_id, ks_proxy): + fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value + fmats_obj._ks = ks_proxy + #Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { "get_reduced_hexagons": get_reduced_hexagons, diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index 9be0bf567fe..a685dc1d48f 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -13,7 +13,6 @@ cimport cython from sage.combinat.root_system.fast_parallel_fmats_methods cimport _fmat from itertools import product -from sage.misc.cachefunc import cached_function from sage.rings.qqbar import QQbar ############### diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 7ca654259d3..7b58bd39b2a 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -5,18 +5,19 @@ from sage.rings.polynomial.polydict cimport ETuple cpdef tuple poly_to_tup(MPolynomial_libsingular poly) cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) -cpdef ETuple get_variables_degrees(list eqns) +cpdef dict get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cpdef bint tup_fixes_sq(tuple eq_tup) -cdef dict subs_squares(dict eq_dict, dict known_sq) +# cdef dict subs_squares(dict eq_dict, dict known_sq) +cdef dict subs_squares(dict eq_dict, known_sq) cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) cpdef tup_to_univ_poly(tuple eq_tup, univ_poly_ring) cpdef tuple poly_tup_sortkey(tuple eq_tup) -cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one) +# cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one) +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, known_sq, NumberFieldElement_absolute one) cdef tuple _flatten_coeffs(tuple eq_tup) cpdef tuple _unflatten_coeffs(field, tuple eq_tup) cdef int has_appropriate_linear_term(tuple eq_tup) - diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 5d662efc772..a812a9d5f28 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -80,7 +80,7 @@ cdef inline tuple _flatten_coeffs(tuple eq_tup): coefficients. This is used to avoid pickling cyclotomic coefficient objects, which fails - with new PARI settings introduced in trac ticket #30537 + with new PARI settings introduced in :trac:`30537`. """ cdef list flat = [] cdef NumberFieldElement_absolute cyc_coeff @@ -232,7 +232,7 @@ cdef inline ETuple degrees(tuple poly_tup): max_degs = max_degs.emax( ( poly_tup[i])[0]) return max_degs -cpdef ETuple get_variables_degrees(list eqns): +cpdef dict get_variables_degrees(list eqns): r""" Find maximum degrees for each variable in equations. @@ -243,16 +243,18 @@ cpdef ETuple get_variables_degrees(list eqns): sage: polys = [x**2 + 1, x*y*z**2 - 4*x*y, x*z**3 - 4/3*y + 1] sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: get_variables_degrees([poly_to_tup(p) for p in polys]) - (2, 1, 3) + {0: 2, 1: 1, 2: 3} """ if not eqns: - return ETuple([]) + # return ETuple([]) + return dict() cdef ETuple max_deg cdef int i max_deg = degrees(eqns[0]) for i in range(1, len(eqns)): max_deg = max_deg.emax(degrees( (eqns[i]) )) - return max_deg + # return max_deg + return { max_deg._data[2*i] : max_deg._data[2*i+1] for i in range(max_deg._nonzero)} cpdef list variables(tuple eq_tup): """ @@ -356,7 +358,8 @@ cpdef inline bint tup_fixes_sq(tuple eq_tup): ### Simplification ### ###################### -cdef dict subs_squares(dict eq_dict, dict known_sq): +# cdef dict subs_squares(dict eq_dict, dict known_sq): +cdef dict subs_squares(dict eq_dict, known_sq): r""" Substitute for known squares into a given polynomial. @@ -437,7 +440,7 @@ cdef tuple to_monic(dict eq_dict, one): ret.append((ord_monoms[n-2-i], inv_lc * eq_dict[ord_monoms[n-2-i]])) return tuple(ret) -cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one): +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, known_sq, NumberFieldElement_absolute one): """ Return a tuple describing a monic polynomial with no known nonzero gcf and no known squares. @@ -475,6 +478,8 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) + sage: from sage.rings.polynomial.polydict import ETuple + sage: max_deg = ETuple(max_deg,R.ngens()) sage: compute_known_powers(max_deg, known_val, R.base_ring().one()) {0: [(((0, 0, 0), 1),), (((0, 0, 0), -1),), @@ -485,9 +490,9 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): (((0, 4, 0), 1),), (((0, 6, 0), 1),)]} """ - if not max_deg: - return {} - assert max(max_deg.nonzero_values(sort=False)) <= 100, "NotImplementedError: Cannot substitute for degree larger than 100" + # if not max_deg: + # return {} + assert max_deg._nonzero and max(max_deg.nonzero_values(sort=False)) <= 100 or True, "NotImplementedError: Cannot substitute for degree larger than 100" max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) cdef dict known_powers #Get polynomial unit as tuple to initialize list elements @@ -606,4 +611,3 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): key.append(-exp._data[2*i]) key.append(exp._data[2*i+1]) return tuple(key) - From 2f101022a28be8c480e1260e3f2a8a466aafdcbc Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 22 Apr 2021 15:06:41 -0400 Subject: [PATCH 050/414] changed factory._solved from set -> indicator list --- src/sage/combinat/root_system/f_matrix.py | 54 ++++++++++++------- .../fast_parallel_fmats_methods.pyx | 15 ++++-- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 231e36c0c0b..a6f68f93e59 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -296,7 +296,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab #Useful solver state attributes self.ideal_basis = list() - self._solved = set() + self._solved = list(False for fx in self._fvars) self._ks = dict() self._kp = dict() self._nnz = self._get_known_nonz() @@ -353,7 +353,8 @@ def clear_vars(self): fx0 """ self._fvars = {self._var_to_sextuple[key] : key for key in self._var_to_sextuple} - self._solved = set() + # self._solved = set() + self._solved = list(False for fx in self._fvars) def _reset_solver_state(self): r""" @@ -376,7 +377,7 @@ def _reset_solver_state(self): True sage: f._poly_ring.base_ring() == K True - sage: len(f._solved) == 0 + sage: sum(f._solved) == 0 True sage: len(f.ideal_basis) == 0 True @@ -904,7 +905,8 @@ def _get_known_vals(self): sage: len(f._get_known_vals()) == f._poly_ring.ngens() True """ - return {var_idx: self._fvars[self._idx_to_sextuple[var_idx]] for var_idx in self._solved} + # return {var_idx: self._fvars[self._idx_to_sextuple[var_idx]] for var_idx in self._solved} + return {var_idx: self._fvars[self._idx_to_sextuple[var_idx]] for var_idx, v in enumerate(self._solved) if v} def _get_known_sq(self,eqns=None): r""" @@ -1117,7 +1119,8 @@ def load_fvars(self, filename): self._fvars, self._non_cyc_roots, self._coerce_map_from_cyc_field, self._qqbar_embedding = pickle.load(f) #Update state attributes self._chkpt_status = 7 - self._solved = set(range(self._poly_ring.ngens())) + # self._solved = set(range(self._poly_ring.ngens())) + self._solved = list(True for v in self._fvars) self._field = self._qqbar_embedding.domain() def get_fr_str(self): @@ -1166,7 +1169,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): Computing appropriate NumberField... sage: f._chkpt_status == 7 True - sage: len(f._solved) == f._poly_ring.ngens() + sage: sum(f._solved) == f._poly_ring.ngens() True sage: os.remove("fmatrix_solver_checkpoint_A13.pickle") sage: f = FMatrix(FusionRing("A1",2)) @@ -1193,7 +1196,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): [1, 1] sage: f._chkpt_status == 7 True - sage: len(f._solved) == f._poly_ring.ngens() + sage: sum(f._solved) == f._poly_ring.ngens() True sage: os.remove("fmatrix_solver_checkpoint_A12.pickle") """ @@ -1864,7 +1867,8 @@ def _get_explicit_solution(self,eqns=None,verbose=True): n = self._poly_ring.ngens() one = self._field.one() for fx, rhs in self._ks.items(): - if fx not in self._solved: + # if fx not in self._solved: + if not self._solved[fx]: lt = (ETuple({fx : 2},n), one) eqns.append((lt, (ETuple({},n), -self._field(list(rhs))))) eqns_partition = self._partition_eqns(verbose=verbose) @@ -1924,9 +1928,11 @@ def _get_explicit_solution(self,eqns=None,verbose=True): self._update_poly_ring_base_field(self._field) #Ensure all F-symbols are known - self._solved.update(numeric_fvars) + # self._solved.update(numeric_fvars) + for fx in numeric_fvars: + self._solved[fx] = True nvars = self._poly_ring.ngens() - assert len(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in set(range(nvars)).difference(self._solved)]) + assert sum(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in range(nvars) if not self._solved[fx]]) #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) self._fvars = {sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items()} @@ -2074,7 +2080,8 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: - print("Hex elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) + # print("Hex elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) + print("Hex elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) self._checkpoint(checkpoint,2,verbose=verbose) @@ -2095,7 +2102,8 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: - print("Pent elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) + # print("Pent elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) + print("Pent elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) self._checkpoint(checkpoint,4,verbose=verbose) @@ -2149,11 +2157,14 @@ def _fix_gauge(self, algorithm=""): adding equation... fx18 - 1 adding equation... fx21 - 1 """ - while len(self._solved) < len(self._poly_ring.gens()): + # while len(self._solved) < len(self._poly_ring.gens()): + while sum(1 for v in self._solved if not v) > 0: #Get a variable that has not been fixed #In ascending index order, for consistent results - for var in self._poly_ring.gens(): - if var not in self._solved: + # for var in self._poly_ring.gens(): + # if var not in self._solved: + for i, var in enumerate(self._poly_ring.gens()): + if not self._solved[i]: break #Fix var = 1, substitute, and solve equations @@ -2187,16 +2198,20 @@ def _substitute_degree_one(self, eqns=None): new_knowns = set() useless = set() for eq in eqns: - if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self._solved: + # if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self._solved: + if eq.degree() == 1 and sum(eq.degrees()) <= 2 and not self._solved[self._var_to_idx[eq.lm()]]: self._fvars[self._var_to_sextuple[eq.lm()]] = -sum(c * m for c, m in zip(eq.coefficients()[1:], eq.monomials()[1:])) / eq.lc() #Add variable to set of known values and remove this equation new_knowns.add(eq.lm()) useless.add(eq) #Update fvars depending on other variables - self._solved.update(new_knowns) + # self._solved.update(new_knowns) + for fx in new_knowns: + self._solved[self._var_to_idx[fx]] = fx for sextuple, rhs in self._fvars.items(): - d = {var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self._solved} + # d = {var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self._solved} + d = {var: self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if self._solved[self._var_to_idx[var]]} if d: self._fvars[sextuple] = rhs.subs(d) return new_knowns, useless @@ -2216,7 +2231,8 @@ def _update_equations(self): sage: f.ideal_basis {fx3} """ - special_values = {known : self._fvars[self._var_to_sextuple[known]] for known in self._solved} + # special_values = {known : self._fvars[self._var_to_sextuple[known]] for known in self._solved} + special_values = {known : self._fvars[self._var_to_sextuple[known]] for known in self._solved if known} self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) self.ideal_basis.discard(0) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index a81599c8e85..c1f4c96ae07 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -74,20 +74,24 @@ cpdef _solve_for_linear_terms(factory, eqns=None): if len(eq_tup) == 1: vars = variables(eq_tup) - if len(vars) == 1 and vars[0] not in factory._solved: + # if len(vars) == 1 and vars[0] not in factory._solved: + if len(vars) == 1 and not factory._solved[vars[0]]: factory._fvars[factory._idx_to_sextuple[vars[0]]] = tuple() - factory._solved.add(vars[0]) + # factory._solved.add(vars[0]) + factory._solved[vars[0]] = True linear_terms_exist = True if len(eq_tup) == 2: idx = has_appropriate_linear_term(eq_tup) if idx < 0: continue #The chosen term is guaranteed to be univariate in the largest variable max_var = eq_tup[idx][0].nonzero_positions()[0] - if max_var not in factory._solved: + # if max_var not in factory._solved: + if not factory._solved[max_var]: rhs_exp = eq_tup[(idx+1) % 2][0] rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] factory._fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) - factory._solved.add(max_var) + # factory._solved.add(max_var) + factory._solved[max_var] = True linear_terms_exist = True return linear_terms_exist @@ -141,7 +145,8 @@ cpdef _backward_subs(factory): sextuple = factory._var_to_sextuple[var] rhs = factory._fvars[sextuple] d = {var_idx: factory._fvars[factory._idx_to_sextuple[var_idx]] - for var_idx in variables(rhs) if var_idx in factory._solved} + # for var_idx in variables(rhs) if var_idx in factory._solved} + for var_idx in variables(rhs) if factory._solved[var_idx]} if d: degs = ETuple(get_variables_degrees([rhs]),n) kp = compute_known_powers(degs, d, one) From a68b93708e9d9a2260fb9febc0300c3fc5f1a0b2 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 22 Apr 2021 15:54:01 -0400 Subject: [PATCH 051/414] _var_degs changed from dict to dense list --- src/sage/combinat/root_system/f_matrix.py | 3 +-- .../fast_parallel_fmats_methods.pyx | 3 +-- .../combinat/root_system/poly_tup_engine.pxd | 4 ++-- .../combinat/root_system/poly_tup_engine.pyx | 20 +++++++++++-------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index a6f68f93e59..54ff59cb699 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1489,8 +1489,7 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat """ if eqns is None: eqns = self.ideal_basis - self._ks = self._get_known_sq(eqns) - self._var_degs = ETuple(get_variables_degrees(eqns),self._poly_ring.ngens()) + self._ks, self._var_degs = self._get_known_sq(eqns), get_variables_degrees(eqns) self._nnz = self._get_known_nonz() self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) if worker_pool is not None and children_need_update: diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index c1f4c96ae07..a777313b5a1 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -148,8 +148,7 @@ cpdef _backward_subs(factory): # for var_idx in variables(rhs) if var_idx in factory._solved} for var_idx in variables(rhs) if factory._solved[var_idx]} if d: - degs = ETuple(get_variables_degrees([rhs]),n) - kp = compute_known_powers(degs, d, one) + kp = compute_known_powers(get_variables_degrees([rhs]), d, one) factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), _ks).items()) cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 7b58bd39b2a..07781084c36 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -5,14 +5,14 @@ from sage.rings.polynomial.polydict cimport ETuple cpdef tuple poly_to_tup(MPolynomial_libsingular poly) cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) -cpdef dict get_variables_degrees(list eqns) +cpdef list get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cpdef bint tup_fixes_sq(tuple eq_tup) # cdef dict subs_squares(dict eq_dict, dict known_sq) cdef dict subs_squares(dict eq_dict, known_sq) -cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one) +cpdef dict compute_known_powers(list max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) cpdef tup_to_univ_poly(tuple eq_tup, univ_poly_ring) cpdef tuple poly_tup_sortkey(tuple eq_tup) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index a812a9d5f28..ee2357b2468 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -232,7 +232,7 @@ cdef inline ETuple degrees(tuple poly_tup): max_degs = max_degs.emax( ( poly_tup[i])[0]) return max_degs -cpdef dict get_variables_degrees(list eqns): +cpdef list get_variables_degrees(list eqns): r""" Find maximum degrees for each variable in equations. @@ -243,18 +243,22 @@ cpdef dict get_variables_degrees(list eqns): sage: polys = [x**2 + 1, x*y*z**2 - 4*x*y, x*z**3 - 4/3*y + 1] sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: get_variables_degrees([poly_to_tup(p) for p in polys]) - {0: 2, 1: 1, 2: 3} + [2, 1, 3] """ if not eqns: # return ETuple([]) - return dict() + return list() cdef ETuple max_deg cdef int i max_deg = degrees(eqns[0]) for i in range(1, len(eqns)): max_deg = max_deg.emax(degrees( (eqns[i]) )) # return max_deg - return { max_deg._data[2*i] : max_deg._data[2*i+1] for i in range(max_deg._nonzero)} + # return { max_deg._data[2*i] : max_deg._data[2*i+1] for i in range(max_deg._nonzero)} + cdef list dense = [0]*len(max_deg) + for i in range(max_deg._nonzero): + dense[max_deg._data[2*i]] = max_deg._data[2*i+1] + return dense cpdef list variables(tuple eq_tup): """ @@ -455,7 +459,7 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, known_sq, NumberFieldElem ### Substitution ### #################### -cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): +cpdef dict compute_known_powers(list max_degs, dict val_dict, one): """ Pre-compute powers of known values for efficiency when preparing to substitute into a list of polynomials. @@ -478,8 +482,6 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) - sage: from sage.rings.polynomial.polydict import ETuple - sage: max_deg = ETuple(max_deg,R.ngens()) sage: compute_known_powers(max_deg, known_val, R.base_ring().one()) {0: [(((0, 0, 0), 1),), (((0, 0, 0), -1),), @@ -492,7 +494,9 @@ cpdef dict compute_known_powers(ETuple max_deg, dict val_dict, one): """ # if not max_deg: # return {} - assert max_deg._nonzero and max(max_deg.nonzero_values(sort=False)) <= 100 or True, "NotImplementedError: Cannot substitute for degree larger than 100" + # assert max_deg._nonzero and max(max_deg.nonzero_values(sort=False)) <= 100 or True, "NotImplementedError: Cannot substitute for degree larger than 100" + assert (max_degs and max(max_degs) <= 100) or True, "Cannot substitute for degree larger than 100" + cdef ETuple max_deg = ETuple(max_degs) max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) cdef dict known_powers #Get polynomial unit as tuple to initialize list elements From 78da266deeb0acd48856bed262ce825d5b113a81 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 23 Apr 2021 14:17:35 -0400 Subject: [PATCH 052/414] _solved and _var_degs using shared_memory... FileNotFoundError in _checkpoint remains --- src/sage/combinat/root_system/f_matrix.py | 51 ++++++++++++++++--- .../fast_parallel_fmats_methods.pyx | 22 +++++--- .../combinat/root_system/poly_tup_engine.pxd | 2 +- .../combinat/root_system/poly_tup_engine.pyx | 4 +- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 54ff59cb699..0ec222fe63c 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -26,7 +26,7 @@ from sage.combinat.root_system.fast_parallel_fmats_methods import ( _backward_subs, _solve_for_linear_terms, - executor + executor, init ) from sage.combinat.root_system.poly_tup_engine import ( apply_coeff_map, constant_coeff, @@ -46,6 +46,8 @@ from sage.rings.polynomial.polydict import ETuple from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics +from multiprocessing import shared_memory + class FMatrix(): def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): r""" @@ -297,6 +299,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab #Useful solver state attributes self.ideal_basis = list() self._solved = list(False for fx in self._fvars) + self._var_degs = [0]*len(self._fvars) self._ks = dict() self._kp = dict() self._nnz = self._get_known_nonz() @@ -1204,7 +1207,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): return filename = "fmatrix_solver_checkpoint_" + self.get_fr_str() + ".pickle" with open(filename, 'wb') as f: - pickle.dump([self._fvars, self._solved, self._ks, self.ideal_basis, status], f) + pickle.dump([self._fvars, list(self._solved), self._ks, self.ideal_basis, status], f) if verbose: print(f"Checkpoint {status} reached!") @@ -1472,12 +1475,19 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat sage: f = FMatrix(FusionRing("A1",3)) sage: f.get_orthogonality_constraints(output=False) - sage: from multiprocessing import Pool, set_start_method + sage: from multiprocessing import cpu_count, Pool, set_start_method, shared_memory sage: try: ....: set_start_method('fork') ....: except: ....: pass - sage: pool = Pool() + sage: n = max(cpu_count()-1,1) + sage: f._solved = shared_memory.ShareableList(f._solved) + sage: s_name = f._solved.shm.name + sage: f._var_degs = shared_memory.ShareableList(f._var_degs) + sage: vd_name = f._var_degs.shm.name + sage: args = (id(f), s_name, vd_name) + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import init + sage: pool = Pool(processes=n,initializer=init,initargs=args) sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey @@ -1489,13 +1499,25 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat """ if eqns is None: eqns = self.ideal_basis - self._ks, self._var_degs = self._get_known_sq(eqns), get_variables_degrees(eqns) + self._ks = self._get_known_sq(eqns) + # print("res",get_variables_degrees(eqns)) + degs = get_variables_degrees(eqns) + if degs: + for i, d in enumerate(degs): + # print(i, d) + self._var_degs[i] = d + else: + for i in range(len(self._fvars)): + self._var_degs[i] = 0 + # print("vd, var_degs") + # self._var_degs = get_variables_degrees(eqns) self._nnz = self._get_known_nonz() self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) if worker_pool is not None and children_need_update: #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes - new_data = [(self._fvars,self._solved,self._ks,self._var_degs)]*n_proc + # new_data = [(self._fvars,self._solved,self._ks,self._var_degs)]*n_proc + new_data = [(self._fvars,self._ks)]*n_proc self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): @@ -2053,7 +2075,18 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start set_start_method('fork') except RuntimeError: pass - pool = Pool(processes=max(cpu_count()-1,1)) if use_mp else None + # pool = Pool(processes=max(cpu_count()-1,1)) if use_mp else None + if use_mp: + n = max(cpu_count()-1,1) + self._solved = shared_memory.ShareableList(self._solved) + print(self._solved) + s_name = self._solved.shm.name + self._var_degs = shared_memory.ShareableList(self._var_degs) + vd_name = self._var_degs.shm.name + args = (id(self), s_name, vd_name) + pool = Pool(processes=n,initializer=init,initargs=args) + else: + pool = None if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) @@ -2111,6 +2144,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Close worker pool to free resources if pool is not None: pool.close() + #Destroy shared resources + self._solved.shm.unlink() + self._var_degs.shm.unlink() #Set up new equations graph and compute variety for each component if self._chkpt_status < 5: @@ -2131,6 +2167,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if save_results: self.save_fvars(save_results) + ######################### ### Cyclotomic method ### ######################### diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index a777313b5a1..c2e7316b4cf 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -29,6 +29,8 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from time import sleep +from multiprocessing import shared_memory + ########################## ### Fast class methods ### ########################## @@ -392,12 +394,19 @@ cpdef update_child_fmats(factory, tuple data_tup): sage: f = FMatrix(FusionRing("A1",3)) sage: f.get_orthogonality_constraints(output=False) - sage: from multiprocessing import Pool, set_start_method + sage: from multiprocessing import cpu_count, Pool, set_start_method, shared_memory sage: try: ....: set_start_method('fork') # context can be set only once ....: except RuntimeError: ....: pass - sage: pool = Pool() + sage: n = max(cpu_count()-1,1) + sage: f._solved = shared_memory.ShareableList(f._solved) + sage: s_name = f._solved.shm.name + sage: f._var_degs = shared_memory.ShareableList(f._var_degs) + sage: vd_name = f._var_degs.shm.name + sage: args = (id(f), s_name, vd_name) + sage: from sage.combinat.root_system.fast_parallel_fmats_methods import init + sage: pool = Pool(processes=n,initializer=init,initargs=args) sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey @@ -411,8 +420,8 @@ cpdef update_child_fmats(factory, tuple data_tup): """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object - factory._fvars, factory._solved, factory._ks, factory._var_degs = data_tup - # factory._fvars, factory._solved, factory._var_degs = data_tup + # factory._fvars, factory._solved, factory._ks, factory._var_degs = data_tup + factory._fvars, factory._ks = data_tup factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) @@ -439,9 +448,10 @@ cdef inline list collect_eqns(list eqns): ### Parallel code executor ### ############################## -def init(fmats_id, ks_proxy): +def init(fmats_id, solved_name, vd_name): fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value - fmats_obj._ks = ks_proxy + fmats_obj._solved = shared_memory.ShareableList(name=solved_name) + fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) #Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 07781084c36..0a768b40851 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -12,7 +12,7 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cpdef bint tup_fixes_sq(tuple eq_tup) # cdef dict subs_squares(dict eq_dict, dict known_sq) cdef dict subs_squares(dict eq_dict, known_sq) -cpdef dict compute_known_powers(list max_degs, dict val_dict, one) +cpdef dict compute_known_powers(max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) cpdef tup_to_univ_poly(tuple eq_tup, univ_poly_ring) cpdef tuple poly_tup_sortkey(tuple eq_tup) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index ee2357b2468..42ee9217782 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -459,7 +459,7 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, known_sq, NumberFieldElem ### Substitution ### #################### -cpdef dict compute_known_powers(list max_degs, dict val_dict, one): +cpdef dict compute_known_powers(max_degs, dict val_dict, one): """ Pre-compute powers of known values for efficiency when preparing to substitute into a list of polynomials. @@ -496,7 +496,7 @@ cpdef dict compute_known_powers(list max_degs, dict val_dict, one): # return {} # assert max_deg._nonzero and max(max_deg.nonzero_values(sort=False)) <= 100 or True, "NotImplementedError: Cannot substitute for degree larger than 100" assert (max_degs and max(max_degs) <= 100) or True, "Cannot substitute for degree larger than 100" - cdef ETuple max_deg = ETuple(max_degs) + cdef ETuple max_deg = ETuple(list(max_degs)) max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) cdef dict known_powers #Get polynomial unit as tuple to initialize list elements From 2ff7cc36e86d060fe3f4f547bc13a48c14a6c387 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 23 Apr 2021 17:49:48 -0400 Subject: [PATCH 053/414] _ks now in shared_memory. must re-implement using single numpy array due to too many open files issue. _checkpoint bug remains --- src/sage/combinat/root_system/f_matrix.py | 29 ++++--- .../fast_parallel_fmats_methods.pyx | 19 +++-- src/sage/combinat/root_system/shm_managers.py | 76 +++++++++++++++++++ 3 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 src/sage/combinat/root_system/shm_managers.py diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 0ec222fe63c..5e8df07744d 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -47,6 +47,7 @@ from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics from multiprocessing import shared_memory +from sage.combinat.root_system.shm_managers import KSHandler class FMatrix(): def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): @@ -300,7 +301,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.ideal_basis = list() self._solved = list(False for fx in self._fvars) self._var_degs = [0]*len(self._fvars) - self._ks = dict() + # self._ks = dict() + self._ks = KSHandler(self) self._kp = dict() self._nnz = self._get_known_nonz() self._chkpt_status = -1 @@ -401,7 +403,8 @@ def _reset_solver_state(self): self._chkpt_status = -1 self.clear_vars() self.clear_equations() - self._ks = dict() + # self._ks = dict() + self._ks.reset() self._nnz = self._get_known_nonz() #Clear relevant caches @@ -942,12 +945,13 @@ def _get_known_sq(self,eqns=None): """ if eqns is None: eqns = self.ideal_basis - ks = deepcopy(self._ks) + # ks = deepcopy(self._ks) F = self._field for eq_tup in eqns: if tup_fixes_sq(eq_tup): - ks[variables(eq_tup)[0]] = tuple(-v for v in eq_tup[-1][1]) - return ks + # ks[variables(eq_tup)[0]] = tuple(-v for v in eq_tup[-1][1]) + self._ks[variables(eq_tup)[0]] = -self._field(list(eq_tup[-1][1])) + # return ks def _get_known_nonz(self): r""" @@ -966,7 +970,8 @@ def _get_known_nonz(self): 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) """ nonz = {self._var_to_idx[var] : 100 for var in self._singles} - for idx in self._ks: + # for idx in self._ks: + for idx, v in self._ks.items(): nonz[idx] = 100 return ETuple(nonz, self._poly_ring.ngens()) @@ -1499,7 +1504,8 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat """ if eqns is None: eqns = self.ideal_basis - self._ks = self._get_known_sq(eqns) + # self._ks = self._get_known_sq(eqns) + self._get_known_sq(eqns) # print("res",get_variables_degrees(eqns)) degs = get_variables_degrees(eqns) if degs: @@ -1517,7 +1523,8 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes # new_data = [(self._fvars,self._solved,self._ks,self._var_degs)]*n_proc - new_data = [(self._fvars,self._ks)]*n_proc + # new_data = [(self._fvars,self._ks)]*n_proc + new_data = [(self._fvars,)]*n_proc self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): @@ -2079,11 +2086,12 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if use_mp: n = max(cpu_count()-1,1) self._solved = shared_memory.ShareableList(self._solved) - print(self._solved) + # print(self._solved) s_name = self._solved.shm.name self._var_degs = shared_memory.ShareableList(self._var_degs) vd_name = self._var_degs.shm.name - args = (id(self), s_name, vd_name) + ks_names = self._ks.get_names() + args = (id(self), s_name, vd_name, ks_names) pool = Pool(processes=n,initializer=init,initargs=args) else: pool = None @@ -2147,6 +2155,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Destroy shared resources self._solved.shm.unlink() self._var_degs.shm.unlink() + self._ks.unlink() #Set up new equations graph and compute variety for each component if self._chkpt_status < 5: diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index c2e7316b4cf..634b2d21eaf 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -30,6 +30,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from time import sleep from multiprocessing import shared_memory +from sage.combinat.root_system.shm_managers import KSHandler ########################## ### Fast class methods ### @@ -140,7 +141,8 @@ cpdef _backward_subs(factory): 1 """ one = factory._field.one() - _ks = {k : factory._FR.field()(list(v)) for k, v in factory._ks.items()} + # _ks = {k : factory._FR.field()(list(v)) for k, v in factory._ks.items()} + _ks = factory._ks vars = factory._poly_ring.gens() n = len(vars) for var in reversed(vars): @@ -243,7 +245,8 @@ cdef get_reduced_hexagons(factory, tuple mp_params): cdef NumberFieldElement_absolute one = _field.one() cdef ETuple _nnz = factory._nnz # cdef dict _ks = factory._ks - _ks = {k : _field(list(v)) for k, v in factory._ks.items()} + # _ks = {k : _field(list(v)) for k, v in factory._ks.items()} + _ks = factory._ks #Computation loop for i, sextuple in enumerate(product(basis, repeat=6)): @@ -297,7 +300,8 @@ cdef get_reduced_pentagons(factory, tuple mp_params): cdef NumberFieldElement_absolute one = _field.one() cdef ETuple _nnz = factory._nnz # cdef dict _ks = factory._ks - _ks = {k : _field(list(v)) for k, v in factory._ks.items()} + # _ks = {k : _field(list(v)) for k, v in factory._ks.items()} + _ks = factory._ks cdef MPolynomial_libsingular zero = factory._poly_ring.zero() #Computation loop @@ -326,7 +330,8 @@ cdef list update_reduce(factory, list eqns): #Pre-compute common parameters for speed _field = factory._field one = _field.one() - _ks = {k : _field(list(v)) for k, v in factory._ks.items()} + # _ks = {k : _field(list(v)) for k, v in factory._ks.items()} + _ks = factory._ks cdef dict _kp = factory._kp cdef ETuple _nnz = factory._nnz @@ -421,7 +426,8 @@ cpdef update_child_fmats(factory, tuple data_tup): #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object # factory._fvars, factory._solved, factory._ks, factory._var_degs = data_tup - factory._fvars, factory._ks = data_tup + # factory._fvars, factory._ks = data_tup + factory._fvars = data_tup[0] factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) @@ -448,10 +454,11 @@ cdef inline list collect_eqns(list eqns): ### Parallel code executor ### ############################## -def init(fmats_id, solved_name, vd_name): +def init(fmats_id, solved_name, vd_name, ks_names): fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value fmats_obj._solved = shared_memory.ShareableList(name=solved_name) fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) + fmats_obj._ks = KSHandler(fmats_obj,ks_names) #Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { diff --git a/src/sage/combinat/root_system/shm_managers.py b/src/sage/combinat/root_system/shm_managers.py new file mode 100644 index 00000000000..83f1dfdb27e --- /dev/null +++ b/src/sage/combinat/root_system/shm_managers.py @@ -0,0 +1,76 @@ +from multiprocessing import shared_memory + +class KSHandler(): + def __init__(self, factory, names=None): + n = len(factory._fvars) + self.field = factory._field + self.index = 0 + if names is None: + self.ks = shared_memory.ShareableList([False]*n) + self.nums = [shared_memory.ShareableList([int(0)]*self.field.degree()) for i in range(n)] + self.denoms = shared_memory.ShareableList([int(0)]*n) + else: + self.ks = shared_memory.ShareableList(name=names[0]) + self.nums = [shared_memory.ShareableList(name=names[1][i]) for i in range(n)] + self.denoms = shared_memory.ShareableList(name=names[2]) + + def __getitem__(self,idx): + if not self.ks[idx]: + raise KeyError('Index {} does not correspond to a known square'.format(idx)) + denom = self.denoms[idx] + return self.field([num / denom for num in self.nums[idx]]) + + def __setitem__(self,idx,rhs): + self.ks[idx] = True + self.denoms[idx] = int(rhs.denominator()) + for i, c in enumerate(rhs._coefficients()): + self.nums[idx][i] = int(c * self.denoms[idx]) + + def __iter__(self): + self.index = 0 + return self + + def __next__(self): + if self.index == len(self.ks): + raise StopIteration + + #Skip indices that are not known + while not self.ks[self.index]: + self.index += 1 + if self.index == len(self.ks): + raise StopIteration + + denom = self.denoms[self.index] + ret = self.field([num / denom for num in self.nums[self.index]]) + self.index += 1 + return ret + + def __contains__(self,idx): + return self.ks[idx] + + def __len__(self): + return sum(self.ks) + + def items(self): + for v in self: + yield self.index-1, v + + def reset(self): + n = len(self.ks) + for i in range(n): + self.ks[i] = False + self.denoms[i] = 0 + for j in range(len(self.nums[i])): + self.nums[i][j] = 0 + + def unlink(self): + self.ks.shm.unlink() + for num_list in self.nums: + num_list.shm.unlink() + self.denoms.shm.unlink() + + def get_names(self): + ks_name = self.ks.shm.name + nums_names = [num_list.shm.name for num_list in self.nums] + denom_name = self.denoms.shm.name + return (ks_name,nums_names,denom_name) From c92392fbf70f2bf4c7e6ce2078164001a8e505d7 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 27 Apr 2021 16:49:25 -0400 Subject: [PATCH 054/414] _ks, _solved, _var_degs in shared_memory. all doctests passing --- src/sage/combinat/root_system/f_matrix.py | 57 ++++++--------- .../fast_parallel_fmats_methods.pyx | 17 +---- .../combinat/root_system/poly_tup_engine.pyx | 6 -- src/sage/combinat/root_system/shm_managers.py | 70 +++++++++++-------- 4 files changed, 63 insertions(+), 87 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 5e8df07744d..c647f94c9fb 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -300,8 +300,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab #Useful solver state attributes self.ideal_basis = list() self._solved = list(False for fx in self._fvars) + # self._solved = np.zeros((len(self._fvars),), dtype='bool') self._var_degs = [0]*len(self._fvars) - # self._ks = dict() self._ks = KSHandler(self) self._kp = dict() self._nnz = self._get_known_nonz() @@ -358,8 +358,8 @@ def clear_vars(self): fx0 """ self._fvars = {self._var_to_sextuple[key] : key for key in self._var_to_sextuple} - # self._solved = set() self._solved = list(False for fx in self._fvars) + # self._solved[:] = np.zeros((len(self._fvars),), dtype='bool') def _reset_solver_state(self): r""" @@ -403,7 +403,6 @@ def _reset_solver_state(self): self._chkpt_status = -1 self.clear_vars() self.clear_equations() - # self._ks = dict() self._ks.reset() self._nnz = self._get_known_nonz() @@ -911,7 +910,6 @@ def _get_known_vals(self): sage: len(f._get_known_vals()) == f._poly_ring.ngens() True """ - # return {var_idx: self._fvars[self._idx_to_sextuple[var_idx]] for var_idx in self._solved} return {var_idx: self._fvars[self._idx_to_sextuple[var_idx]] for var_idx, v in enumerate(self._solved) if v} def _get_known_sq(self,eqns=None): @@ -940,18 +938,16 @@ def _get_known_sq(self,eqns=None): fx10*fx11 + fx12*fx13, fx11^2 + fx13^2 - 1] sage: f.get_orthogonality_constraints(output=False) - sage: len(f._get_known_sq()) == 10 + sage: f._get_known_sq() + sage: len(f._ks) == 10 True """ if eqns is None: eqns = self.ideal_basis - # ks = deepcopy(self._ks) F = self._field for eq_tup in eqns: if tup_fixes_sq(eq_tup): - # ks[variables(eq_tup)[0]] = tuple(-v for v in eq_tup[-1][1]) - self._ks[variables(eq_tup)[0]] = -self._field(list(eq_tup[-1][1])) - # return ks + self._ks[variables(eq_tup)[0]] = eq_tup[-1][1] def _get_known_nonz(self): r""" @@ -970,7 +966,6 @@ def _get_known_nonz(self): 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) """ nonz = {self._var_to_idx[var] : 100 for var in self._singles} - # for idx in self._ks: for idx, v in self._ks.items(): nonz[idx] = 100 return ETuple(nonz, self._poly_ring.ngens()) @@ -1127,8 +1122,8 @@ def load_fvars(self, filename): self._fvars, self._non_cyc_roots, self._coerce_map_from_cyc_field, self._qqbar_embedding = pickle.load(f) #Update state attributes self._chkpt_status = 7 - # self._solved = set(range(self._poly_ring.ngens())) self._solved = list(True for v in self._fvars) + # self._solved = np.zeros((len(self._fvars),), dtype='bool') self._field = self._qqbar_embedding.domain() def get_fr_str(self): @@ -1212,7 +1207,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): return filename = "fmatrix_solver_checkpoint_" + self.get_fr_str() + ".pickle" with open(filename, 'wb') as f: - pickle.dump([self._fvars, list(self._solved), self._ks, self.ideal_basis, status], f) + pickle.dump([self._fvars, self._solved, self._ks, self.ideal_basis, status], f) if verbose: print(f"Checkpoint {status} reached!") @@ -1490,7 +1485,7 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat sage: s_name = f._solved.shm.name sage: f._var_degs = shared_memory.ShareableList(f._var_degs) sage: vd_name = f._var_degs.shm.name - sage: args = (id(f), s_name, vd_name) + sage: args = (id(f), s_name, vd_name, f._ks.get_names()) sage: from sage.combinat.root_system.fast_parallel_fmats_methods import init sage: pool = Pool(processes=n,initializer=init,initargs=args) sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) @@ -1504,26 +1499,19 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat """ if eqns is None: eqns = self.ideal_basis - # self._ks = self._get_known_sq(eqns) self._get_known_sq(eqns) - # print("res",get_variables_degrees(eqns)) degs = get_variables_degrees(eqns) if degs: for i, d in enumerate(degs): - # print(i, d) self._var_degs[i] = d else: for i in range(len(self._fvars)): self._var_degs[i] = 0 - # print("vd, var_degs") - # self._var_degs = get_variables_degrees(eqns) self._nnz = self._get_known_nonz() self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) if worker_pool is not None and children_need_update: #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes - # new_data = [(self._fvars,self._solved,self._ks,self._var_degs)]*n_proc - # new_data = [(self._fvars,self._ks)]*n_proc new_data = [(self._fvars,)]*n_proc self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) @@ -1956,7 +1944,6 @@ def _get_explicit_solution(self,eqns=None,verbose=True): self._update_poly_ring_base_field(self._field) #Ensure all F-symbols are known - # self._solved.update(numeric_fvars) for fx in numeric_fvars: self._solved[fx] = True nvars = self._poly_ring.ngens() @@ -2086,8 +2073,11 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if use_mp: n = max(cpu_count()-1,1) self._solved = shared_memory.ShareableList(self._solved) - # print(self._solved) + # self._solved_shm = shared_memory.SharedMemory(create=True,size=len(self._fvars)) + # self._solved = np.ndarray((len(self._fvars),), dtype='bool', buffer=self._solved_shm.buf) + # self._solved[:] = np.zeros((len(self._fvars),), dtype='bool') s_name = self._solved.shm.name + # s_name = self._solved_shm.name self._var_degs = shared_memory.ShareableList(self._var_degs) vd_name = self._var_degs.shm.name ks_names = self._ks.get_names() @@ -2149,14 +2139,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Try adding degrevlex gb -> elim loop until len(ideal_basis) does not change - #Close worker pool to free resources - if pool is not None: - pool.close() - #Destroy shared resources - self._solved.shm.unlink() - self._var_degs.shm.unlink() - self._ks.unlink() - #Set up new equations graph and compute variety for each component if self._chkpt_status < 5: self.ideal_basis = self._par_graph_gb(term_order="lex",verbose=verbose) @@ -2176,6 +2158,14 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if save_results: self.save_fvars(save_results) + #Close worker pool to free resources + if pool is not None: + pool.close() + #Destroy shared resources + self._solved.shm.unlink() + # self._solved_shm.unlink() + self._var_degs.shm.unlink() + self._ks.unlink() ######################### ### Cyclotomic method ### @@ -2204,10 +2194,9 @@ def _fix_gauge(self, algorithm=""): """ # while len(self._solved) < len(self._poly_ring.gens()): while sum(1 for v in self._solved if not v) > 0: + # while self._solved.sum() < self._solved.size: #Get a variable that has not been fixed #In ascending index order, for consistent results - # for var in self._poly_ring.gens(): - # if var not in self._solved: for i, var in enumerate(self._poly_ring.gens()): if not self._solved[i]: break @@ -2243,7 +2232,6 @@ def _substitute_degree_one(self, eqns=None): new_knowns = set() useless = set() for eq in eqns: - # if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self._solved: if eq.degree() == 1 and sum(eq.degrees()) <= 2 and not self._solved[self._var_to_idx[eq.lm()]]: self._fvars[self._var_to_sextuple[eq.lm()]] = -sum(c * m for c, m in zip(eq.coefficients()[1:], eq.monomials()[1:])) / eq.lc() #Add variable to set of known values and remove this equation @@ -2251,11 +2239,9 @@ def _substitute_degree_one(self, eqns=None): useless.add(eq) #Update fvars depending on other variables - # self._solved.update(new_knowns) for fx in new_knowns: self._solved[self._var_to_idx[fx]] = fx for sextuple, rhs in self._fvars.items(): - # d = {var : self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self._solved} d = {var: self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if self._solved[self._var_to_idx[var]]} if d: self._fvars[sextuple] = rhs.subs(d) @@ -2276,7 +2262,6 @@ def _update_equations(self): sage: f.ideal_basis {fx3} """ - # special_values = {known : self._fvars[self._var_to_sextuple[known]] for known in self._solved} special_values = {known : self._fvars[self._var_to_sextuple[known]] for known in self._solved if known} self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) self.ideal_basis.discard(0) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 634b2d21eaf..7a070724c6c 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -77,10 +77,8 @@ cpdef _solve_for_linear_terms(factory, eqns=None): if len(eq_tup) == 1: vars = variables(eq_tup) - # if len(vars) == 1 and vars[0] not in factory._solved: if len(vars) == 1 and not factory._solved[vars[0]]: factory._fvars[factory._idx_to_sextuple[vars[0]]] = tuple() - # factory._solved.add(vars[0]) factory._solved[vars[0]] = True linear_terms_exist = True if len(eq_tup) == 2: @@ -88,12 +86,10 @@ cpdef _solve_for_linear_terms(factory, eqns=None): if idx < 0: continue #The chosen term is guaranteed to be univariate in the largest variable max_var = eq_tup[idx][0].nonzero_positions()[0] - # if max_var not in factory._solved: if not factory._solved[max_var]: rhs_exp = eq_tup[(idx+1) % 2][0] rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] factory._fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) - # factory._solved.add(max_var) factory._solved[max_var] = True linear_terms_exist = True return linear_terms_exist @@ -141,7 +137,6 @@ cpdef _backward_subs(factory): 1 """ one = factory._field.one() - # _ks = {k : factory._FR.field()(list(v)) for k, v in factory._ks.items()} _ks = factory._ks vars = factory._poly_ring.gens() n = len(vars) @@ -149,7 +144,6 @@ cpdef _backward_subs(factory): sextuple = factory._var_to_sextuple[var] rhs = factory._fvars[sextuple] d = {var_idx: factory._fvars[factory._idx_to_sextuple[var_idx]] - # for var_idx in variables(rhs) if var_idx in factory._solved} for var_idx in variables(rhs) if factory._solved[var_idx]} if d: kp = compute_known_powers(get_variables_degrees([rhs]), d, one) @@ -244,8 +238,6 @@ cdef get_reduced_hexagons(factory, tuple mp_params): _field = factory._field cdef NumberFieldElement_absolute one = _field.one() cdef ETuple _nnz = factory._nnz - # cdef dict _ks = factory._ks - # _ks = {k : _field(list(v)) for k, v in factory._ks.items()} _ks = factory._ks #Computation loop @@ -299,8 +291,6 @@ cdef get_reduced_pentagons(factory, tuple mp_params): _field = factory._field cdef NumberFieldElement_absolute one = _field.one() cdef ETuple _nnz = factory._nnz - # cdef dict _ks = factory._ks - # _ks = {k : _field(list(v)) for k, v in factory._ks.items()} _ks = factory._ks cdef MPolynomial_libsingular zero = factory._poly_ring.zero() @@ -330,7 +320,6 @@ cdef list update_reduce(factory, list eqns): #Pre-compute common parameters for speed _field = factory._field one = _field.one() - # _ks = {k : _field(list(v)) for k, v in factory._ks.items()} _ks = factory._ks cdef dict _kp = factory._kp cdef ETuple _nnz = factory._nnz @@ -409,7 +398,9 @@ cpdef update_child_fmats(factory, tuple data_tup): sage: s_name = f._solved.shm.name sage: f._var_degs = shared_memory.ShareableList(f._var_degs) sage: vd_name = f._var_degs.shm.name - sage: args = (id(f), s_name, vd_name) + sage: from sage.combinat.root_system.shm_managers import KSHandler + sage: f._ks = KSHandler(f) + sage: args = (id(f), s_name, vd_name, f._ks.get_names()) sage: from sage.combinat.root_system.fast_parallel_fmats_methods import init sage: pool = Pool(processes=n,initializer=init,initargs=args) sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) @@ -425,8 +416,6 @@ cpdef update_child_fmats(factory, tuple data_tup): """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object - # factory._fvars, factory._solved, factory._ks, factory._var_degs = data_tup - # factory._fvars, factory._ks = data_tup factory._fvars = data_tup[0] factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 42ee9217782..519b5023e94 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -246,15 +246,12 @@ cpdef list get_variables_degrees(list eqns): [2, 1, 3] """ if not eqns: - # return ETuple([]) return list() cdef ETuple max_deg cdef int i max_deg = degrees(eqns[0]) for i in range(1, len(eqns)): max_deg = max_deg.emax(degrees( (eqns[i]) )) - # return max_deg - # return { max_deg._data[2*i] : max_deg._data[2*i+1] for i in range(max_deg._nonzero)} cdef list dense = [0]*len(max_deg) for i in range(max_deg._nonzero): dense[max_deg._data[2*i]] = max_deg._data[2*i+1] @@ -492,9 +489,6 @@ cpdef dict compute_known_powers(max_degs, dict val_dict, one): (((0, 4, 0), 1),), (((0, 6, 0), 1),)]} """ - # if not max_deg: - # return {} - # assert max_deg._nonzero and max(max_deg.nonzero_values(sort=False)) <= 100 or True, "NotImplementedError: Cannot substitute for degree larger than 100" assert (max_degs and max(max_degs) <= 100) or True, "Cannot substitute for degree larger than 100" cdef ETuple max_deg = ETuple(list(max_degs)) max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) diff --git a/src/sage/combinat/root_system/shm_managers.py b/src/sage/combinat/root_system/shm_managers.py index 83f1dfdb27e..bdbd08cae41 100644 --- a/src/sage/combinat/root_system/shm_managers.py +++ b/src/sage/combinat/root_system/shm_managers.py @@ -1,30 +1,42 @@ from multiprocessing import shared_memory +import numpy as np +from sage.misc.cachefunc import cached_method class KSHandler(): def __init__(self, factory, names=None): - n = len(factory._fvars) self.field = factory._field self.index = 0 + n = len(factory._fvars) + d = self.field.degree() + coeff_dtype = np.dtype([('nums','i4',(d,)), ('denoms','u4',(d,))]) if names is None: - self.ks = shared_memory.ShareableList([False]*n) - self.nums = [shared_memory.ShareableList([int(0)]*self.field.degree()) for i in range(n)] - self.denoms = shared_memory.ShareableList([int(0)]*n) + self.ks_shm = shared_memory.SharedMemory(create=True,size=n) + self.ks = np.ndarray((n,),dtype='bool',buffer=self.ks_shm.buf) + self.ks[:] = np.zeros((n,),dtype='bool') + self.coeff_shm = shared_memory.SharedMemory(create=True,size=2*n*d*4) + self.coeffs = np.ndarray((n,),dtype=coeff_dtype,buffer=self.coeff_shm.buf) + self.coeffs['nums'][:] = np.zeros((n,d),dtype='i4') + self.coeffs['denoms'][:] = np.ones((n,d),dtype='u4') else: - self.ks = shared_memory.ShareableList(name=names[0]) - self.nums = [shared_memory.ShareableList(name=names[1][i]) for i in range(n)] - self.denoms = shared_memory.ShareableList(name=names[2]) + self.ks_shm = shared_memory.SharedMemory(name=names[0]) + self.ks = np.ndarray((n,),dtype='bool',buffer=self.ks_shm.buf) + self.coeff_shm = shared_memory.SharedMemory(name=names[1]) + self.coeffs = np.ndarray((n,),dtype=coeff_dtype,buffer=self.coeff_shm.buf) - def __getitem__(self,idx): + @cached_method + def __getitem__(self, idx): if not self.ks[idx]: raise KeyError('Index {} does not correspond to a known square'.format(idx)) - denom = self.denoms[idx] - return self.field([num / denom for num in self.nums[idx]]) + rat = list() + for i in range(self.field.degree()): + rat.append(self.coeffs['nums'][idx][i] / self.coeffs['denoms'][idx][i]) + return self.field(rat) - def __setitem__(self,idx,rhs): + def __setitem__(self, idx, rhs): self.ks[idx] = True - self.denoms[idx] = int(rhs.denominator()) - for i, c in enumerate(rhs._coefficients()): - self.nums[idx][i] = int(c * self.denoms[idx]) + for i, c in enumerate(rhs): + self.coeffs['nums'][idx][i] = -c.numerator() + self.coeffs['denoms'][idx][i] = c.denominator() def __iter__(self): self.index = 0 @@ -40,37 +52,33 @@ def __next__(self): if self.index == len(self.ks): raise StopIteration - denom = self.denoms[self.index] - ret = self.field([num / denom for num in self.nums[self.index]]) self.index += 1 - return ret + return self[self.index-1] - def __contains__(self,idx): + def __contains__(self, idx): return self.ks[idx] def __len__(self): return sum(self.ks) + def __eq__(self, other): + return np.all(self.ks == other.ks) and np.all(self.coeffs == other.coeffs) + def items(self): for v in self: yield self.index-1, v def reset(self): n = len(self.ks) - for i in range(n): - self.ks[i] = False - self.denoms[i] = 0 - for j in range(len(self.nums[i])): - self.nums[i][j] = 0 + d = self.field.degree() + self.ks[:] = np.zeros((n,),dtype='bool') + self.__getitem__.clear_cache() + self.coeffs['nums'][:] = np.zeros((n,d),dtype='i4') + self.coeffs['denoms'][:] = np.ones((n,d),dtype='u4') def unlink(self): - self.ks.shm.unlink() - for num_list in self.nums: - num_list.shm.unlink() - self.denoms.shm.unlink() + self.ks_shm.unlink() + self.coeff_shm.unlink() def get_names(self): - ks_name = self.ks.shm.name - nums_names = [num_list.shm.name for num_list in self.nums] - denom_name = self.denoms.shm.name - return (ks_name,nums_names,denom_name) + return (self.ks_shm.name,self.coeff_shm.name) From 7ec815b141c0fea9641d6fa88984e43f5ecd1994 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 27 Apr 2021 17:12:11 -0400 Subject: [PATCH 055/414] some deprecated code removed --- src/sage/combinat/root_system/f_matrix.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index c647f94c9fb..a485f3fbc48 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1883,7 +1883,6 @@ def _get_explicit_solution(self,eqns=None,verbose=True): n = self._poly_ring.ngens() one = self._field.one() for fx, rhs in self._ks.items(): - # if fx not in self._solved: if not self._solved[fx]: lt = (ETuple({fx : 2},n), one) eqns.append((lt, (ETuple({},n), -self._field(list(rhs))))) @@ -2069,7 +2068,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start set_start_method('fork') except RuntimeError: pass - # pool = Pool(processes=max(cpu_count()-1,1)) if use_mp else None if use_mp: n = max(cpu_count()-1,1) self._solved = shared_memory.ShareableList(self._solved) @@ -2110,7 +2108,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: - # print("Hex elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) print("Hex elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) self._checkpoint(checkpoint,2,verbose=verbose) @@ -2132,7 +2129,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: - # print("Pent elim step solved for {} / {} variables".format(len(self._solved), len(self._poly_ring.gens()))) print("Pent elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) self._checkpoint(checkpoint,4,verbose=verbose) @@ -2158,10 +2154,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if save_results: self.save_fvars(save_results) - #Close worker pool to free resources + #Close worker pool and destroy shared resources if pool is not None: pool.close() - #Destroy shared resources self._solved.shm.unlink() # self._solved_shm.unlink() self._var_degs.shm.unlink() From ba0b572b0731d0737c019b426a15c38d7780f72d Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 28 Apr 2021 02:12:56 -0400 Subject: [PATCH 056/414] reduced zipping/unzipping of fvars, moved sovler state attributes out of constructor. leaked memory issue remains --- src/sage/combinat/root_system/f_matrix.py | 177 ++++++++++-------- .../fast_parallel_fmats_methods.pyx | 35 ++-- 2 files changed, 117 insertions(+), 95 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index a485f3fbc48..0aee09da311 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -297,14 +297,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._qqbar_embedding = self._field.hom([r], QQbar) self._non_cyc_roots = list() - #Useful solver state attributes - self.ideal_basis = list() - self._solved = list(False for fx in self._fvars) - # self._solved = np.zeros((len(self._fvars),), dtype='bool') - self._var_degs = [0]*len(self._fvars) - self._ks = KSHandler(self) - self._kp = dict() - self._nnz = self._get_known_nonz() + #Warm starting self._chkpt_status = -1 #Multiprocessing attributes @@ -369,6 +362,7 @@ def _reset_solver_state(self): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",1)) + sage: f._reset_solver_state() sage: K = f.field() sage: len(f._nnz.nonzero_positions()) 1 @@ -403,7 +397,9 @@ def _reset_solver_state(self): self._chkpt_status = -1 self.clear_vars() self.clear_equations() - self._ks.reset() + self._var_degs = [0]*len(self._fvars) + self._kp = dict() + self._ks = KSHandler(self) self._nnz = self._get_known_nonz() #Clear relevant caches @@ -904,6 +900,7 @@ def _get_known_vals(self): EXAMPLES:: sage: f = FMatrix(FusionRing("D4",1)) + sage: f._reset_solver_state() sage: len(f._get_known_vals()) == 0 True sage: f.find_orthogonal_solution(verbose=False) @@ -920,6 +917,7 @@ def _get_known_sq(self,eqns=None): EXAMPLES:: sage: f = FMatrix(FusionRing("B5",1)) + sage: f._reset_solver_state() sage: len(f._ks) == 0 True sage: f.get_orthogonality_constraints() @@ -961,6 +959,7 @@ def _get_known_nonz(self): EXAMPLES:: sage: f = FMatrix(FusionRing("D5",1)) # indirect doctest + sage: f._reset_solver_state() sage: f._nnz (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) @@ -1020,20 +1019,24 @@ def get_fvars_by_size(self,n,indices=False): (f4, f4, f4, f4, f4, f0), (f4, f4, f4, f4, f4, f4)} """ - fvars_copy = deepcopy(self._fvars) - solved_copy = deepcopy(self._solved) - self.clear_vars() var_set = set() - for quadruple in product(self._FR.basis(),repeat=4): - F = self.fmatrix(*quadruple) - #Discard trivial 1x1 F-matrix, if applicable - if F.nrows() == n and F.coefficients() != [1]: - var_set.update(F.coefficients()) - self._fvars = fvars_copy - self._solved = solved_copy + one = self._FR.one() + for a,b,c,d in product(self._FR.basis(),repeat=4): + X = self.f_from(a,b,c,d) + Y = self.f_to(a,b,c,d) + if len(X) == n and len(Y) == n: + for x in X: + for y in Y: + #Discard trivial 1x1 F-matrix + trivial = a == one and x == b and y == d + trivial |= b == one and x == a and y == c + trivial |= c == one and x == d and y == b + if not trivial: + var_set.add((a,b,c,d,x,y)) if indices: - return {self._var_to_idx[fx] for fx in var_set} - return {self._var_to_sextuple[fx] for fx in var_set} + sext_to_idx = {v: k for k, v in self._idx_to_sextuple.items()} + return {sext_to_idx[fx] for fx in var_set} + return var_set ############################ ### Checkpoint utilities ### @@ -1150,8 +1153,9 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons',output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params(children_need_update=False) sage: f._checkpoint(do_chkpt=True,status=2) @@ -1182,6 +1186,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params(children_need_update=False) sage: f.get_defining_equations('pentagons',output=False) @@ -1223,8 +1228,9 @@ def _restore_state(self,filename): sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons',output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params(children_need_update=False) sage: fvars = f._fvars @@ -1236,6 +1242,7 @@ def _restore_state(self,filename): Checkpoint 2 reached! sage: del f sage: f = FMatrix(FusionRing("A1",2)) + sage: f._reset_solver_state() sage: f._restore_state("fmatrix_solver_checkpoint_A12.pickle") sage: fvars == f._fvars True @@ -1274,6 +1281,50 @@ def _restore_state(self,filename): ### MapReduce ### ################# + def get_worker_pool(self,processes=None): + """ + Get a worker pool for parallel processing, which may be used e.g. + to set up defining equations using :meth:`get_defining_equations`. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("G2",1)) + sage: pool = f.get_worker_pool() + sage: he = f.get_defining_equations('hexagons',worker_pool=pool) + sage: sorted(he) + [fx0 - 1, + fx2*fx3 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx4^2 + (zeta60^6)*fx4, + fx1*fx3 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx3*fx4 + (zeta60^14 - zeta60^4)*fx3, + fx1*fx2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx4 + (zeta60^14 - zeta60^4)*fx2, + fx1^2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx3 + (-zeta60^12)*fx1] + + .. WARNING:: + + This method is needed to initialize the worker pool using the + necessary shared memory resources. Simply using the + ``multiprocessing.Pool`` constructor will not work with our class + methods. + """ + try: + set_start_method('fork') + except RuntimeError: + pass + if not hasattr(self, '_nnz'): + self._reset_solver_state() + self._solved = shared_memory.ShareableList(self._solved) + # self._solved_shm = shared_memory.SharedMemory(create=True,size=len(self._fvars)) + # self._solved = np.ndarray((len(self._fvars),), dtype='bool', buffer=self._solved_shm.buf) + # self._solved[:] = np.zeros((len(self._fvars),), dtype='bool') + s_name = self._solved.shm.name + # s_name = self._solved_shm.name + self._var_degs = shared_memory.ShareableList(self._var_degs) + vd_name = self._var_degs.shm.name + ks_names = self._ks.get_names() + args = (id(self), s_name, vd_name, ks_names) + n = cpu_count() if processes is None else processes + pool = Pool(processes=n,initializer=init,initargs=args) + return pool + def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): r""" Apply the given mapper to each element of the given input iterable and @@ -1297,6 +1348,7 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t EXAMPLES:: sage: f = FMatrix(FusionRing("A1",2)) + sage: f._reset_solver_state() sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1)])) 11 sage: from multiprocessing import Pool @@ -1424,6 +1476,8 @@ def get_defining_equations(self,option,worker_pool=None,output=True): sage: len(pe) 33 """ + if not hasattr(self, '_nnz'): + self._reset_solver_state() n_proc = worker_pool._processes if worker_pool is not None else 1 params = [(child_id, n_proc) for child_id in range(n_proc)] eqns = self._map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) @@ -1455,12 +1509,7 @@ def _tup_to_fpoly(self,eq_tup): sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f = FMatrix(FusionRing("C3",1)) - sage: from multiprocessing import Pool, set_start_method - sage: try: - ....: set_start_method('fork') - ....: except: - ....: pass - sage: pool = Pool() + sage: pool = f.get_worker_pool() sage: he = f.get_defining_equations('hexagons',pool) sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True @@ -1474,25 +1523,15 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat EXAMPLES:: sage: f = FMatrix(FusionRing("A1",3)) + sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: from multiprocessing import cpu_count, Pool, set_start_method, shared_memory - sage: try: - ....: set_start_method('fork') - ....: except: - ....: pass - sage: n = max(cpu_count()-1,1) - sage: f._solved = shared_memory.ShareableList(f._solved) - sage: s_name = f._solved.shm.name - sage: f._var_degs = shared_memory.ShareableList(f._var_degs) - sage: vd_name = f._var_degs.shm.name - sage: args = (id(f), s_name, vd_name, f._ks.get_names()) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import init - sage: pool = Pool(processes=n,initializer=init,initargs=args) + sage: pool = f.get_worker_pool() sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(worker_pool=pool,verbose=False) # indirect doctest sage: f.ideal_basis [] @@ -1531,8 +1570,9 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): sage: f.get_defining_equations('hexagons',output=False) sage: f.get_orthogonality_constraints(output=False) sage: gb = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis = sorted(gb, key=poly_tup_sortkey) + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim() Elimination epoch completed... 0 eqns remain in ideal basis sage: f.ideal_basis @@ -1543,7 +1583,7 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): eqns = self.ideal_basis ret = False #Unzip polynomials - self._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items()} + # self._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items()} while True: linear_terms_exist = _solve_for_linear_terms(self,eqns) @@ -1561,7 +1601,7 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) #Zip up _fvars before exiting - self._fvars = {sextuple : self._tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items()} + # self._fvars = {sextuple : self._tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items()} if ret: return eqns self.ideal_basis = eqns @@ -1701,13 +1741,9 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose EXAMPLES:: sage: f = FMatrix(FusionRing("F4",1)) + sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: from multiprocessing import Pool, set_start_method - sage: try: - ....: set_start_method('fork') # context can be set only once - ....: except RuntimeError: - ....: pass - sage: pool = Pool() + sage: pool = f.get_worker_pool() sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: gb = f._par_graph_gb(worker_pool=pool) Partitioned 10 equations into 2 components of size: @@ -1762,12 +1798,8 @@ def _get_component_variety(self,var,eqns): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",2)) - sage: from multiprocessing import Pool, set_start_method - sage: try: - ....: set_start_method('fork') # context can be set only once - ....: except RuntimeError: - ....: pass - sage: f.get_defining_equations('hexagons',worker_pool=Pool(),output=False) # long time + sage: pool = f.get_worker_pool() + sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) # long time sage: partition = f._partition_eqns() # long time Partitioned 327 equations into 35 components of size: [27, 27, 27, 24, 24, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, @@ -1949,7 +1981,8 @@ def _get_explicit_solution(self,eqns=None,verbose=True): assert sum(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in range(nvars) if not self._solved[fx]]) #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) - self._fvars = {sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items()} + # self._fvars = {sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items()} + self._fvars = {sextuple : apply_coeff_map(rhs,phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({},nvars),rhs),) _backward_subs(self) @@ -2063,26 +2096,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if self._chkpt_status > 5: return - #Set multiprocessing parameters. Context can only be set once, so we try to set it - try: - set_start_method('fork') - except RuntimeError: - pass - if use_mp: - n = max(cpu_count()-1,1) - self._solved = shared_memory.ShareableList(self._solved) - # self._solved_shm = shared_memory.SharedMemory(create=True,size=len(self._fvars)) - # self._solved = np.ndarray((len(self._fvars),), dtype='bool', buffer=self._solved_shm.buf) - # self._solved[:] = np.zeros((len(self._fvars),), dtype='bool') - s_name = self._solved.shm.name - # s_name = self._solved_shm.name - self._var_degs = shared_memory.ShareableList(self._var_degs) - vd_name = self._var_degs.shm.name - ks_names = self._ks.get_names() - args = (id(self), s_name, vd_name, ks_names) - pool = Pool(processes=n,initializer=init,initargs=args) - else: - pool = None + pool = self.get_worker_pool(max(cpu_count()-1,1)) if use_mp else None if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) @@ -2090,6 +2104,8 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Set up hexagon equations and orthogonality constraints self.get_orthogonality_constraints(output=False) self.get_defining_equations('hexagons',worker_pool=pool,output=False) + #Unzip fvars + self._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items()} #Report progress if verbose: @@ -2214,6 +2230,7 @@ def _substitute_degree_one(self, eqns=None): sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 + sage: f._reset_solver_state() sage: f.ideal_basis = [fx0 - 8, fx4**2 - 3, fx4 + fx10 + 3, fx4 + fx9] sage: _, _ = f._substitute_degree_one() sage: f._fvars[f._var_to_sextuple[fx0]] @@ -2251,6 +2268,7 @@ def _update_equations(self): sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 + sage: f._reset_solver_state() sage: f.ideal_basis = [fx0 - 8, fx4 + fx9, fx4**2 + fx3 - fx9**2] sage: _, _ = f._substitute_degree_one() sage: f._update_equations() @@ -2336,6 +2354,7 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o print("Done!") if output: return self._fvars + self._ks.unlink() ##################### ### Verifications ### diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 7a070724c6c..4687472dc14 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -46,6 +46,7 @@ cpdef _solve_for_linear_terms(factory, eqns=None): sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 + sage: f._reset_solver_state() sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] @@ -104,6 +105,7 @@ cpdef _backward_subs(factory): sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 + sage: f._reset_solver_state() sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] @@ -285,7 +287,17 @@ cdef get_reduced_pentagons(factory, tuple mp_params): #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) - cdef dict fvars = factory._fvars + # cdef dict fvars = factory._fvars + #Handle both cyclotomic and orthogonal solution method + cdef bint must_zip_up + for k in factory._fvars: + must_zip_up = isinstance(factory._fvars[k], tuple) + break + cdef dict fvars + if must_zip_up: + fvars = {k: _tup_to_poly(v,parent=factory._poly_ring) for k, v in factory._fvars.items()} + else: + fvars = factory._fvars _Nk_ij = factory._FR.Nk_ij id_anyon = factory._FR.one() _field = factory._field @@ -387,27 +399,16 @@ cpdef update_child_fmats(factory, tuple data_tup): TESTS:: sage: f = FMatrix(FusionRing("A1",3)) + sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: from multiprocessing import cpu_count, Pool, set_start_method, shared_memory - sage: try: - ....: set_start_method('fork') # context can be set only once - ....: except RuntimeError: - ....: pass - sage: n = max(cpu_count()-1,1) - sage: f._solved = shared_memory.ShareableList(f._solved) - sage: s_name = f._solved.shm.name - sage: f._var_degs = shared_memory.ShareableList(f._var_degs) - sage: vd_name = f._var_degs.shm.name - sage: from sage.combinat.root_system.shm_managers import KSHandler - sage: f._ks = KSHandler(f) - sage: args = (id(f), s_name, vd_name, f._ks.get_names()) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import init - sage: pool = Pool(processes=n,initializer=init,initargs=args) + sage: pool = f.get_worker_pool() sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 + sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(worker_pool=pool) # indirect doctest Elimination epoch completed... 10 eqns remain in ideal basis Elimination epoch completed... 0 eqns remain in ideal basis @@ -486,10 +487,12 @@ cpdef executor(tuple params): sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor sage: fmats = FMatrix(FusionRing("A1",3)) + sage: fmats._reset_solver_state() sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) sage: len(executor(params)) == 63 True sage: fmats = FMatrix(FusionRing("E6",1)) + sage: fmats._reset_solver_state() sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) sage: len(executor(params)) == 6 True From 54d6473154834466604e7ce5a11ef19e5e59e044 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 29 Apr 2021 14:03:42 -0400 Subject: [PATCH 057/414] improved _get_known_vals method --- src/sage/combinat/root_system/f_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 0aee09da311..85a51af6862 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -907,7 +907,7 @@ def _get_known_vals(self): sage: len(f._get_known_vals()) == f._poly_ring.ngens() True """ - return {var_idx: self._fvars[self._idx_to_sextuple[var_idx]] for var_idx, v in enumerate(self._solved) if v} + return {i: self._fvars[s] for i, s in self._idx_to_sextuple.items() if self._solved[i]} def _get_known_sq(self,eqns=None): r""" From d7bc58d3ebafd59d5b9a4453f2de455975fb28f7 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 29 Apr 2021 15:03:16 -0400 Subject: [PATCH 058/414] _fvars in shared_memory, both solvers working. some doctests failing, fvars_handler in separate pyx --- src/sage/combinat/root_system/f_matrix.py | 14 ++++++++++++-- .../root_system/fast_parallel_fmats_methods.pyx | 15 +++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 85a51af6862..59c458ecc77 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -48,6 +48,7 @@ from multiprocessing import shared_memory from sage.combinat.root_system.shm_managers import KSHandler +from sage.combinat.root_system.fvars_handler import FvarsHandler class FMatrix(): def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): @@ -1320,7 +1321,9 @@ def get_worker_pool(self,processes=None): self._var_degs = shared_memory.ShareableList(self._var_degs) vd_name = self._var_degs.shm.name ks_names = self._ks.get_names() - args = (id(self), s_name, vd_name, ks_names) + self._shared_fvars = FvarsHandler(self) + fvar_names = self._shared_fvars.get_names() + args = (id(self), s_name, vd_name, ks_names, fvar_names) n = cpu_count() if processes is None else processes pool = Pool(processes=n,initializer=init,initargs=args) return pool @@ -1551,7 +1554,8 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat if worker_pool is not None and children_need_update: #self._nnz and self._kp are computed in child processes to reduce IPC overhead n_proc = worker_pool._processes - new_data = [(self._fvars,)]*n_proc + # new_data = [(self._fvars,)]*n_proc + new_data = [(1,)]*n_proc self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): @@ -2107,6 +2111,11 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Unzip fvars self._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items()} + #Initialize shared fvars handler + for sextuple, fvar in self._fvars.items(): + self._shared_fvars[sextuple] = fvar + self._fvars = self._shared_fvars + #Report progress if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) @@ -2177,6 +2186,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start # self._solved_shm.unlink() self._var_degs.shm.unlink() self._ks.unlink() + self._shared_fvars.unlink() ######################### ### Cyclotomic method ### diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 4687472dc14..9e490d5debe 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -31,6 +31,7 @@ from time import sleep from multiprocessing import shared_memory from sage.combinat.root_system.shm_managers import KSHandler +from sage.combinat.root_system.fvars_handler import FvarsHandler ########################## ### Fast class methods ### @@ -233,7 +234,8 @@ cdef get_reduced_hexagons(factory, tuple mp_params): #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) - cdef dict fvars = factory._fvars + # cdef dict fvars = factory._fvars + cdef dict fvars = {v: k for k, v in factory._var_to_sextuple.items()} r_matrix = factory._FR.r_matrix _Nk_ij = factory._FR.Nk_ij id_anyon = factory._FR.one() @@ -290,8 +292,8 @@ cdef get_reduced_pentagons(factory, tuple mp_params): # cdef dict fvars = factory._fvars #Handle both cyclotomic and orthogonal solution method cdef bint must_zip_up - for k in factory._fvars: - must_zip_up = isinstance(factory._fvars[k], tuple) + for k, v in factory._fvars.items(): + must_zip_up = isinstance(v, tuple) break cdef dict fvars if must_zip_up: @@ -417,12 +419,12 @@ cpdef update_child_fmats(factory, tuple data_tup): """ #factory object is assumed global before forking used to create the Pool object, #so each child has a global fmats variable. So it's enough to update that object - factory._fvars = data_tup[0] + # factory._fvars = data_tup[0] factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) #Wait this process isn't used again - sleep(0.25) + sleep(0.5) ################ ### Reducers ### @@ -444,11 +446,12 @@ cdef inline list collect_eqns(list eqns): ### Parallel code executor ### ############################## -def init(fmats_id, solved_name, vd_name, ks_names): +def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value fmats_obj._solved = shared_memory.ShareableList(name=solved_name) fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) fmats_obj._ks = KSHandler(fmats_obj,ks_names) + fmats_obj._fvars = FvarsHandler(fmats_obj,name=fvar_names) #Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { From fa984df990eb22294ba4a06771437a1ce4133f3d Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 29 Apr 2021 17:18:49 -0400 Subject: [PATCH 059/414] both solvers working w fvars_handler in shm_managers. memory leak fixed --- src/sage/combinat/root_system/f_matrix.py | 122 ++++++++---- .../fast_parallel_fmats_methods.pyx | 29 +-- src/sage/combinat/root_system/shm_managers.py | 84 -------- .../combinat/root_system/shm_managers.pyx | 179 ++++++++++++++++++ 4 files changed, 277 insertions(+), 137 deletions(-) delete mode 100644 src/sage/combinat/root_system/shm_managers.py create mode 100644 src/sage/combinat/root_system/shm_managers.pyx diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 59c458ecc77..e54c1c71ee2 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -19,6 +19,7 @@ import pickle from copy import deepcopy +from ctypes import cast, py_object from itertools import product, zip_longest from multiprocessing import cpu_count, Pool, set_start_method import numpy as np @@ -26,7 +27,7 @@ from sage.combinat.root_system.fast_parallel_fmats_methods import ( _backward_subs, _solve_for_linear_terms, - executor, init + executor ) from sage.combinat.root_system.poly_tup_engine import ( apply_coeff_map, constant_coeff, @@ -47,8 +48,8 @@ from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics from multiprocessing import shared_memory -from sage.combinat.root_system.shm_managers import KSHandler -from sage.combinat.root_system.fvars_handler import FvarsHandler +from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler +# from sage.combinat.root_system.fvars_handler import FvarsHandler class FMatrix(): def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): @@ -353,7 +354,6 @@ def clear_vars(self): """ self._fvars = {self._var_to_sextuple[key] : key for key in self._var_to_sextuple} self._solved = list(False for fx in self._fvars) - # self._solved[:] = np.zeros((len(self._fvars),), dtype='bool') def _reset_solver_state(self): r""" @@ -400,7 +400,8 @@ def _reset_solver_state(self): self.clear_equations() self._var_degs = [0]*len(self._fvars) self._kp = dict() - self._ks = KSHandler(self) + # self._ks = KSHandler(self) + self._ks = dict() self._nnz = self._get_known_nonz() #Clear relevant caches @@ -1127,7 +1128,6 @@ def load_fvars(self, filename): #Update state attributes self._chkpt_status = 7 self._solved = list(True for v in self._fvars) - # self._solved = np.zeros((len(self._fvars),), dtype='bool') self._field = self._qqbar_embedding.domain() def get_fr_str(self): @@ -1213,7 +1213,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): return filename = "fmatrix_solver_checkpoint_" + self.get_fr_str() + ".pickle" with open(filename, 'wb') as f: - pickle.dump([self._fvars, self._solved, self._ks, self.ideal_basis, status], f) + pickle.dump([self._fvars, list(self._solved), self._ks, self.ideal_basis, status], f) if verbose: print(f"Checkpoint {status} reached!") @@ -1284,8 +1284,15 @@ def _restore_state(self,filename): def get_worker_pool(self,processes=None): """ - Get a worker pool for parallel processing, which may be used e.g. - to set up defining equations using :meth:`get_defining_equations`. + Get an initialized worker pool for parallel processing, + which may be used e.g. to set up defining equations using + :meth:`get_defining_equations`. + + More than one task may be submitted to the same worker pool. + + When you are done using the worker pool, use + :meth:`shutdown_worker_pool` to close the pool and properly dispose + of shared memory resources. EXAMPLES:: @@ -1298,6 +1305,8 @@ def get_worker_pool(self,processes=None): fx1*fx3 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx3*fx4 + (zeta60^14 - zeta60^4)*fx3, fx1*fx2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx4 + (zeta60^14 - zeta60^4)*fx2, fx1^2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx3 + (-zeta60^12)*fx1] + sage: pe = f.get_defining_equations('pentaongs',worker_pool=pool) + sage: f.shutdown_worker_pool(pool) .. WARNING:: @@ -1305,6 +1314,12 @@ def get_worker_pool(self,processes=None): necessary shared memory resources. Simply using the ``multiprocessing.Pool`` constructor will not work with our class methods. + + .. WARNING:: + + Failure to call :meth:`shutdown_worker_pool` may result in a memory + leak, since shared memory resources outlive the process that created + them. """ try: set_start_method('fork') @@ -1312,22 +1327,65 @@ def get_worker_pool(self,processes=None): pass if not hasattr(self, '_nnz'): self._reset_solver_state() + #Set up shared memory resource handlers self._solved = shared_memory.ShareableList(self._solved) - # self._solved_shm = shared_memory.SharedMemory(create=True,size=len(self._fvars)) - # self._solved = np.ndarray((len(self._fvars),), dtype='bool', buffer=self._solved_shm.buf) - # self._solved[:] = np.zeros((len(self._fvars),), dtype='bool') s_name = self._solved.shm.name - # s_name = self._solved_shm.name self._var_degs = shared_memory.ShareableList(self._var_degs) vd_name = self._var_degs.shm.name - ks_names = self._ks.get_names() + ks = KSHandler(self) + for idx, val in self._ks.items(): + ks[idx] = val + self._ks = ks + ks_names = self._ks.shm.name self._shared_fvars = FvarsHandler(self) - fvar_names = self._shared_fvars.get_names() + for sextuple, fvar in self._fvars.items(): + if self._chkpt_status < 0: + self._shared_fvars[sextuple] = poly_to_tup(fvar) + else: + self._shared_fvars[sextuple] = fvar + fvar_names = self._shared_fvars.shm.name + #Initialize worker pool processes args = (id(self), s_name, vd_name, ks_names, fvar_names) n = cpu_count() if processes is None else processes + def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): + """ + Connect worker process to shared memory resources + """ + fmats_obj = cast(fmats_id, py_object).value + fmats_obj._solved = shared_memory.ShareableList(name=solved_name) + fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) + fmats_obj._ks = KSHandler(fmats_obj,name=ks_names) + fmats_obj._fvars = FvarsHandler(fmats_obj,name=fvar_names) + pool = Pool(processes=n,initializer=init,initargs=args) return pool + def shutdown_worker_pool(self,pool): + """ + Shutdown the given worker pool and dispose of shared memory resources + created when the pool was set up using :meth:`get_worker_pool`. + + .. WARNING:: + + Failure to call this method after using :meth:`get_worker_pool` + to create a process pool may result in a memory + leak, since shared memory resources outlive the process that created + them. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1",3)) + sage: pool = f.get_worker_pool() + sage: he = f.get_defining_equations('hexagons', worker_pool=pool) + sage: f.shutdown_worker_pool(pool) + """ + pool.close() + self._solved.shm.unlink() + self._var_degs.shm.unlink() + self._ks.shm.unlink() + self._shared_fvars.shm.unlink() + del self.__dict__['_shared_fvars'] + def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): r""" Apply the given mapper to each element of the given input iterable and @@ -1457,8 +1515,9 @@ def get_defining_equations(self,option,worker_pool=None,output=True): Otherwise, the constraints are appended to ``self.ideal_basis``. They are stored in the internal tuple representation. The - ``output=False`` option is meant mostly for internal use by the - F-matrix solver. + ``output=False`` option is meant only for internal use by the + F-matrix solver. When computing the hexagon equations with the + ``output=False`` option, the initial state of the F-symbols is used. EXAMPLES:: @@ -1482,7 +1541,7 @@ def get_defining_equations(self,option,worker_pool=None,output=True): if not hasattr(self, '_nnz'): self._reset_solver_state() n_proc = worker_pool._processes if worker_pool is not None else 1 - params = [(child_id, n_proc) for child_id in range(n_proc)] + params = [(child_id, n_proc, output) for child_id in range(n_proc)] eqns = self._map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) if output: F = self._field @@ -1586,9 +1645,6 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): if eqns is None: eqns = self.ideal_basis ret = False - #Unzip polynomials - # self._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items()} - while True: linear_terms_exist = _solve_for_linear_terms(self,eqns) if not linear_terms_exist: @@ -1603,9 +1659,6 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): eqns.sort(key=poly_tup_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) - - #Zip up _fvars before exiting - # self._fvars = {sextuple : self._tup_to_fpoly(rhs_tup) for sextuple, rhs_tup in self._fvars.items()} if ret: return eqns self.ideal_basis = eqns @@ -1985,7 +2038,6 @@ def _get_explicit_solution(self,eqns=None,verbose=True): assert sum(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in range(nvars) if not self._solved[fx]]) #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) - # self._fvars = {sextuple : apply_coeff_map(poly_to_tup(rhs),phi) for sextuple, rhs in self._fvars.items()} self._fvars = {sextuple : apply_coeff_map(rhs,phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({},nvars),rhs),) @@ -2108,12 +2160,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Set up hexagon equations and orthogonality constraints self.get_orthogonality_constraints(output=False) self.get_defining_equations('hexagons',worker_pool=pool,output=False) - #Unzip fvars - self._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in self._fvars.items()} - - #Initialize shared fvars handler - for sextuple, fvar in self._fvars.items(): - self._shared_fvars[sextuple] = fvar self._fvars = self._shared_fvars #Report progress @@ -2168,6 +2214,10 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self._checkpoint(checkpoint,5,verbose=verbose) + #Close worker pool and destroy shared resources + if use_mp: + self.shutdown_worker_pool(pool) + #Find numeric values for each F-symbol self._get_explicit_solution(verbose=verbose) @@ -2179,15 +2229,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if save_results: self.save_fvars(save_results) - #Close worker pool and destroy shared resources - if pool is not None: - pool.close() - self._solved.shm.unlink() - # self._solved_shm.unlink() - self._var_degs.shm.unlink() - self._ks.unlink() - self._shared_fvars.unlink() - ######################### ### Cyclotomic method ### ######################### @@ -2364,7 +2405,6 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o print("Done!") if output: return self._fvars - self._ks.unlink() ##################### ### Verifications ### diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 9e490d5debe..98bd95357dd 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -30,8 +30,8 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from time import sleep from multiprocessing import shared_memory -from sage.combinat.root_system.shm_managers import KSHandler -from sage.combinat.root_system.fvars_handler import FvarsHandler +from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler +# from sage.combinat.root_system.fvars_handler import FvarsHandler ########################## ### Fast class methods ### @@ -229,13 +229,25 @@ cdef get_reduced_hexagons(factory, tuple mp_params): cdef list worker_results = list() cdef int child_id, n_proc cdef unsigned long i - child_id, n_proc = mp_params + child_id, n_proc, output = mp_params cdef tuple sextuple, red #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) # cdef dict fvars = factory._fvars - cdef dict fvars = {v: k for k, v in factory._var_to_sextuple.items()} + cdef dict fvars + cdef bint must_zip_up + if not output: + fvars = {v: k for k, v in factory._var_to_sextuple.items()} + else: + #Handle both cyclotomic and orthogonal solution method + for k, v in factory._fvars.items(): + must_zip_up = isinstance(v, tuple) + break + if must_zip_up: + fvars = {k: _tup_to_poly(v,parent=factory._poly_ring) for k, v in factory._fvars.items()} + else: + fvars = factory._fvars r_matrix = factory._FR.r_matrix _Nk_ij = factory._FR.Nk_ij id_anyon = factory._FR.one() @@ -282,7 +294,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): #Set up multiprocessing parameters cdef list worker_results = list() cdef int child_id, n_proc - child_id, n_proc = mp_params + child_id, n_proc, output = mp_params cdef unsigned long i cdef tuple nonuple, red cdef MPolynomial_libsingular pe @@ -446,13 +458,6 @@ cdef inline list collect_eqns(list eqns): ### Parallel code executor ### ############################## -def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): - fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value - fmats_obj._solved = shared_memory.ShareableList(name=solved_name) - fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) - fmats_obj._ks = KSHandler(fmats_obj,ks_names) - fmats_obj._fvars = FvarsHandler(fmats_obj,name=fvar_names) - #Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { "get_reduced_hexagons": get_reduced_hexagons, diff --git a/src/sage/combinat/root_system/shm_managers.py b/src/sage/combinat/root_system/shm_managers.py deleted file mode 100644 index bdbd08cae41..00000000000 --- a/src/sage/combinat/root_system/shm_managers.py +++ /dev/null @@ -1,84 +0,0 @@ -from multiprocessing import shared_memory -import numpy as np -from sage.misc.cachefunc import cached_method - -class KSHandler(): - def __init__(self, factory, names=None): - self.field = factory._field - self.index = 0 - n = len(factory._fvars) - d = self.field.degree() - coeff_dtype = np.dtype([('nums','i4',(d,)), ('denoms','u4',(d,))]) - if names is None: - self.ks_shm = shared_memory.SharedMemory(create=True,size=n) - self.ks = np.ndarray((n,),dtype='bool',buffer=self.ks_shm.buf) - self.ks[:] = np.zeros((n,),dtype='bool') - self.coeff_shm = shared_memory.SharedMemory(create=True,size=2*n*d*4) - self.coeffs = np.ndarray((n,),dtype=coeff_dtype,buffer=self.coeff_shm.buf) - self.coeffs['nums'][:] = np.zeros((n,d),dtype='i4') - self.coeffs['denoms'][:] = np.ones((n,d),dtype='u4') - else: - self.ks_shm = shared_memory.SharedMemory(name=names[0]) - self.ks = np.ndarray((n,),dtype='bool',buffer=self.ks_shm.buf) - self.coeff_shm = shared_memory.SharedMemory(name=names[1]) - self.coeffs = np.ndarray((n,),dtype=coeff_dtype,buffer=self.coeff_shm.buf) - - @cached_method - def __getitem__(self, idx): - if not self.ks[idx]: - raise KeyError('Index {} does not correspond to a known square'.format(idx)) - rat = list() - for i in range(self.field.degree()): - rat.append(self.coeffs['nums'][idx][i] / self.coeffs['denoms'][idx][i]) - return self.field(rat) - - def __setitem__(self, idx, rhs): - self.ks[idx] = True - for i, c in enumerate(rhs): - self.coeffs['nums'][idx][i] = -c.numerator() - self.coeffs['denoms'][idx][i] = c.denominator() - - def __iter__(self): - self.index = 0 - return self - - def __next__(self): - if self.index == len(self.ks): - raise StopIteration - - #Skip indices that are not known - while not self.ks[self.index]: - self.index += 1 - if self.index == len(self.ks): - raise StopIteration - - self.index += 1 - return self[self.index-1] - - def __contains__(self, idx): - return self.ks[idx] - - def __len__(self): - return sum(self.ks) - - def __eq__(self, other): - return np.all(self.ks == other.ks) and np.all(self.coeffs == other.coeffs) - - def items(self): - for v in self: - yield self.index-1, v - - def reset(self): - n = len(self.ks) - d = self.field.degree() - self.ks[:] = np.zeros((n,),dtype='bool') - self.__getitem__.clear_cache() - self.coeffs['nums'][:] = np.zeros((n,d),dtype='i4') - self.coeffs['denoms'][:] = np.ones((n,d),dtype='u4') - - def unlink(self): - self.ks_shm.unlink() - self.coeff_shm.unlink() - - def get_names(self): - return (self.ks_shm.name,self.coeff_shm.name) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx new file mode 100644 index 00000000000..52efb52a8a4 --- /dev/null +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -0,0 +1,179 @@ +from cysignals.memory cimport sig_malloc +from sage.rings.polynomial.polydict cimport ETuple + +from multiprocessing import shared_memory +import numpy as np +from sage.misc.cachefunc import cached_method + +class KSHandler(): + def __init__(self, factory, name=None): + self.field = factory._field + self.index = 0 + n = len(factory._fvars) + d = self.field.degree() + ks_t = np.dtype([ + ('known', 'bool', (1,)), + ('nums','i4',(d,)), + ('denoms','u4',(d,)) + ]) + if name is None: + self.shm = shared_memory.SharedMemory(create=True,size=n*ks_t.itemsize) + self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) + self.reset() + else: + self.shm = shared_memory.SharedMemory(name=name) + self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) + + @cached_method + def __getitem__(self, idx): + if not self.ks_dat['known'][idx]: + raise KeyError('Index {} does not correspond to a known square'.format(idx)) + cdef list rat = list() + for i in range(self.field.degree()): + rat.append(self.ks_dat['nums'][idx][i] / self.ks_dat['denoms'][idx][i]) + return self.field(rat) + + def __setitem__(self, idx, rhs): + self.ks_dat['known'][idx] = True + cdef int i + for i in range(len(rhs)): + self.ks_dat['nums'][idx][i] = -rhs[i].numerator() + self.ks_dat['denoms'][idx][i] = rhs[i].denominator() + + def __iter__(self): + self.index = 0 + return self + + def __next__(self): + if self.index == self.ks_dat.size: + raise StopIteration + + #Skip indices that are not known + while not self.ks_dat['known'][self.index]: + self.index += 1 + if self.index == self.ks_dat.size: + raise StopIteration + + self.index += 1 + return self[self.index-1] + + def __contains__(self, idx): + return self.ks_dat['known'][idx] + + def __len__(self): + return sum(self.ks_dat['known']) + + def __eq__(self, other): + return np.all(self.ks_dat == other.ks_dat) + + def items(self): + for v in self: + yield self.index-1, v + + def reset(self): + n = self.ks_dat.size + d = self.field.degree() + self.__getitem__.clear_cache() + self.ks_dat['known'] = np.zeros((n,1),dtype='bool') + self.ks_dat['nums'] = np.zeros((n,d),dtype='i4') + self.ks_dat['denoms'] = np.ones((n,d),dtype='u4') + +class FvarsHandler(): + # cdef dict sext_to_idx + # cdef int ngens + # cdef field, fvars, fvars_t, shm + + def __init__(self, factory, name=None, max_terms=20): + n = len(factory._fvars) + d = factory._field.degree() + self.obj_cache = dict() + self.fvars_t = np.dtype([ + ('modified','bool',(1,)), + ('ticks', 'u1', (max_terms,)), + ('exp_data', 'u2', (max_terms*50,)), + ('coeff_nums','i4',(max_terms,d)), + ('coeff_denom','u4',(max_terms,)) + ]) + self.sext_to_idx = {s: i for i, s in factory._idx_to_sextuple.items()} + self.ngens = factory._poly_ring.ngens() + self.field = factory._field + if name is None: + self.shm = shared_memory.SharedMemory(create=True,size=n*self.fvars_t.itemsize) + self.fvars = np.ndarray((n,),dtype=self.fvars_t,buffer=self.shm.buf) + else: + self.shm = shared_memory.SharedMemory(name=name) + self.fvars = np.ndarray((n,),dtype=self.fvars_t,buffer=self.shm.buf) + + #Given a tuple of labels and a tuple of (ETuple, cyc_coeff) pairs, insert into + #shared dictionary + #WARNING: current data structure supports up to 2**16-1 entries, + #each with each monomial in each entry having at most 254 nonzero terms. + def __setitem__(self, sextuple, fvar): + cdef int cum, denom, i, j, k, idx + cdef ETuple exp + idx = self.sext_to_idx[sextuple] + #Clear entry before inserting + self.fvars[idx] = np.zeros((1,), dtype=self.fvars_t) + cum = 0 + i = 0 + for exp, coeff in fvar: + #Handle constant coefficient + if exp._nonzero > 0: + self.fvars['ticks'][idx][i] = exp._nonzero + else: + self.fvars['ticks'][idx][i] = -1 + for j in range(2*exp._nonzero): + self.fvars['exp_data'][idx][cum] = exp._data[j] + cum += 1 + denom = coeff.denominator() + self.fvars['coeff_denom'][idx][i] = denom + for k, c in enumerate(coeff._coefficients()): + self.fvars['coeff_nums'][idx][i,k] = c * denom + i += 1 + self.fvars['modified'][idx] = True + + #Retrieve a record by unflattening and constructing relevant Python objects + #Only the parent is allowed to modify the shared _fvars object, so to implement caching + #we must tag modified objects and delete cache if present before retrieval + def __getitem__(self, sextuple): + if not sextuple in self.sext_to_idx: + raise KeyError('Invalid sextuple {}'.format(sextuple)) + cdef int idx = self.sext_to_idx[sextuple] + if idx in self.obj_cache: + if self.fvars['modified'][idx]: + del self.obj_cache[idx] + else: + return self.obj_cache[idx] + cdef ETuple e = ETuple({}, self.ngens) + cdef int cum, d, i, j + cdef list poly_tup = list() + cum = 0 + for i in range(np.count_nonzero(self.fvars['ticks'][idx])): + #Construct new ETuple for each monomial + exp = e._new() + #Handle constant coeff + nnz = self.fvars['ticks'][idx][i] if self.fvars['ticks'][idx][i] < 255 else 0 + exp._nonzero = nnz + exp._data = sig_malloc(sizeof(int)*nnz*2) + for j in range(2*nnz): + exp._data[j] = self.fvars['exp_data'][idx][cum] + cum += 1 + + #Construct cyclotomic field coefficient + d = self.fvars['coeff_denom'][idx][i] + cyc_coeff = self.field([num / d for num in self.fvars['coeff_nums'][idx][i]]) + + poly_tup.append((exp, cyc_coeff)) + cdef tuple ret = tuple(poly_tup) + self.obj_cache[idx] = ret + return ret + + def __len__(self): + return self.fvars.size + + def __repr__(self): + return str({sextuple: self[sextuple] for sextuple in self.sext_to_idx}) + + def items(self): + for sextuple in self.sext_to_idx: + yield sextuple, self[sextuple] From 8b70e802d4c85b7a8e69fc2e612df4b09f59fcf6 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 30 Apr 2021 16:34:18 -0400 Subject: [PATCH 060/414] removed update_child_fmats method --- src/sage/combinat/root_system/f_matrix.py | 90 +++---- .../fast_parallel_fmats_methods.pxd | 1 - .../fast_parallel_fmats_methods.pyx | 55 +--- .../combinat/root_system/poly_tup_engine.pxd | 2 - .../combinat/root_system/poly_tup_engine.pyx | 1 - .../combinat/root_system/shm_managers.pyx | 239 ++++++++++++++---- 6 files changed, 240 insertions(+), 148 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index e54c1c71ee2..ca61dab5793 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -17,13 +17,12 @@ import cPickle as pickle except: import pickle - from copy import deepcopy from ctypes import cast, py_object from itertools import product, zip_longest -from multiprocessing import cpu_count, Pool, set_start_method +from multiprocessing import cpu_count, Pool, set_start_method, shared_memory import numpy as np -import os +from os import remove from sage.combinat.root_system.fast_parallel_fmats_methods import ( _backward_subs, _solve_for_linear_terms, @@ -39,6 +38,7 @@ tup_fixes_sq, resize, ) +from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler from sage.graphs.graph import Graph from sage.matrix.constructor import matrix from sage.misc.misc import get_main_globals @@ -47,10 +47,6 @@ from sage.rings.polynomial.polydict import ETuple from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics -from multiprocessing import shared_memory -from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler -# from sage.combinat.root_system.fvars_handler import FvarsHandler - class FMatrix(): def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): r""" @@ -398,9 +394,8 @@ def _reset_solver_state(self): self._chkpt_status = -1 self.clear_vars() self.clear_equations() - self._var_degs = [0]*len(self._fvars) + self._var_degs = [0]*self._poly_ring.ngens() self._kp = dict() - # self._ks = KSHandler(self) self._ks = dict() self._nnz = self._get_known_nonz() @@ -944,10 +939,15 @@ def _get_known_sq(self,eqns=None): """ if eqns is None: eqns = self.ideal_basis - F = self._field for eq_tup in eqns: if tup_fixes_sq(eq_tup): - self._ks[variables(eq_tup)[0]] = eq_tup[-1][1] + #Handle mp and single process cases + if isinstance(self._ks, dict): + rhs = -self._field(list(eq_tup[-1][1])) + else: + rhs = [-v for v in eq_tup[-1][1]] + self._ks[variables(eq_tup)[0]] = rhs + def _get_known_nonz(self): r""" @@ -1288,7 +1288,7 @@ def get_worker_pool(self,processes=None): which may be used e.g. to set up defining equations using :meth:`get_defining_equations`. - More than one task may be submitted to the same worker pool. + This pool may be reused time and again. When you are done using the worker pool, use :meth:`shutdown_worker_pool` to close the pool and properly dispose @@ -1305,7 +1305,7 @@ def get_worker_pool(self,processes=None): fx1*fx3 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx3*fx4 + (zeta60^14 - zeta60^4)*fx3, fx1*fx2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx4 + (zeta60^14 - zeta60^4)*fx2, fx1^2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx3 + (-zeta60^12)*fx1] - sage: pe = f.get_defining_equations('pentaongs',worker_pool=pool) + sage: pe = f.get_defining_equations('pentagons',worker_pool=pool) sage: f.shutdown_worker_pool(pool) .. WARNING:: @@ -1334,6 +1334,8 @@ def get_worker_pool(self,processes=None): vd_name = self._var_degs.shm.name ks = KSHandler(self) for idx, val in self._ks.items(): + if not isinstance(val, tuple): + val = val._coefficients() ks[idx] = val self._ks = ks ks_names = self._ks.shm.name @@ -1410,13 +1412,13 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t sage: f = FMatrix(FusionRing("A1",2)) sage: f._reset_solver_state() - sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1)])) + sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1,False)])) 11 - sage: from multiprocessing import Pool - sage: pool = Pool() - sage: mp_params = [(i,pool._processes) for i in range(pool._processes)] + sage: pool = f.get_worker_pool() + sage: mp_params = [(i,pool._processes,True) for i in range(pool._processes)] sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=pool,chunksize=1,mp_thresh=0)) 33 + sage: f.shutdown_worker_pool(pool) """ if mp_thresh is None: mp_thresh = self.mp_thresh @@ -1593,10 +1595,11 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: f._fvars = f._shared_fvars sage: f._triangular_elim(worker_pool=pool,verbose=False) # indirect doctest sage: f.ideal_basis [] + sage: f.shutdown_worker_pool(pool) """ if eqns is None: eqns = self.ideal_basis @@ -1606,16 +1609,10 @@ def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_updat for i, d in enumerate(degs): self._var_degs[i] = d else: - for i in range(len(self._fvars)): + for i in range(self._poly_ring.ngens()): self._var_degs[i] = 0 self._nnz = self._get_known_nonz() self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) - if worker_pool is not None and children_need_update: - #self._nnz and self._kp are computed in child processes to reduce IPC overhead - n_proc = worker_pool._processes - # new_data = [(self._fvars,)]*n_proc - new_data = [(1,)]*n_proc - self._map_triv_reduce('update_child_fmats',new_data,worker_pool=worker_pool,chunksize=1,mp_thresh=0) def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): r""" @@ -1646,6 +1643,9 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): eqns = self.ideal_basis ret = False while True: + #Reset modification cache + if worker_pool is not None: + self._fvars.fvars['modified'][:] = False linear_terms_exist = _solve_for_linear_terms(self,eqns) if not linear_terms_exist: break @@ -1653,8 +1653,16 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): #Compute new reduction params, send to child processes if any, and update eqns self._update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) - n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) - eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] + # n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) + # eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] + if worker_pool is not None and len(eqns) > self.mp_thresh: + n = worker_pool._processes + chunks = [[] for i in range(n)] + for i, eq_tup in enumerate(eqns): + chunks[i%n].append(eq_tup) + eqns = chunks + else: + eqns = [eqns] eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool) eqns.sort(key=poly_tup_sortkey) if verbose: @@ -2151,21 +2159,22 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Loading from a pickle with solved F-symbols if self._chkpt_status > 5: return - - pool = self.get_worker_pool(max(cpu_count()-1,1)) if use_mp else None + #max(cpu_count()-1,1) + pool = self.get_worker_pool() if use_mp else None if verbose: - print("Computing F-symbols for {} with {} variables...".format(self._FR, len(self._fvars))) + print("Computing F-symbols for {} with {} variables...".format(self._FR, self._poly_ring.ngens())) if self._chkpt_status < 1: #Set up hexagon equations and orthogonality constraints self.get_orthogonality_constraints(output=False) self.get_defining_equations('hexagons',worker_pool=pool,output=False) - self._fvars = self._shared_fvars - #Report progress if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) - + #Unzip _fvars and link to shared_memory structure if using multiprocessing + self._fvars = {sextuple: poly_to_tup(fvar) for sextuple, fvar in self._fvars.items()} + if use_mp: + self._fvars = self._shared_fvars self._checkpoint(checkpoint,1,verbose=verbose) if self._chkpt_status < 2: @@ -2173,10 +2182,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self.ideal_basis = self._par_graph_gb(worker_pool=pool,verbose=verbose) self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(worker_pool=pool,verbose=verbose) - - #Update reduction parameters, also in children if any - self._update_reduction_params(worker_pool=pool,children_need_update=True) - #Report progress if verbose: print("Hex elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) @@ -2187,7 +2192,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Set up pentagon equations in parallel self.get_defining_equations('pentagons',worker_pool=pool,output=False) self.ideal_basis.sort(key=poly_tup_sortkey) - #Report progress if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) @@ -2197,7 +2201,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Simplify and eliminate variables if self._chkpt_status < 4: self._triangular_elim(worker_pool=pool,verbose=verbose) - #Report progress if verbose: print("Pent elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) @@ -2214,18 +2217,17 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self._checkpoint(checkpoint,5,verbose=verbose) - #Close worker pool and destroy shared resources - if use_mp: - self.shutdown_worker_pool(pool) - #Find numeric values for each F-symbol self._get_explicit_solution(verbose=verbose) #The calculation was successful, so we may delete checkpoints self._chkpt_status = 7 self.clear_equations() + #Close worker pool and destroy shared resources + if use_mp: + self.shutdown_worker_pool(pool) if checkpoint: - os.remove("fmatrix_solver_checkpoint_"+self.get_fr_str()+".pickle") + remove("fmatrix_solver_checkpoint_"+self.get_fr_str()+".pickle") if save_results: self.save_fvars(save_results) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index faed5a56f5e..f54fa2ff174 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -1,3 +1,2 @@ -cpdef update_child_fmats(factory, tuple data_tup) cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) cpdef executor(tuple params) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 98bd95357dd..02d2ebc799f 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -22,16 +22,12 @@ from sage.rings.number_field.number_field_element cimport NumberFieldElement_abs from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple -import ctypes +from ctypes import cast, py_object from itertools import product -from sage.rings.ideal import Ideal -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - -from time import sleep - from multiprocessing import shared_memory +from sage.rings.ideal import Ideal from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler -# from sage.combinat.root_system.fvars_handler import FvarsHandler +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing ########################## ### Fast class methods ### @@ -316,6 +312,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): id_anyon = factory._FR.one() _field = factory._field cdef NumberFieldElement_absolute one = _field.one() + factory._nnz = factory._get_known_nonz() cdef ETuple _nnz = factory._nnz _ks = factory._ks cdef MPolynomial_libsingular zero = factory._poly_ring.zero() @@ -347,6 +344,9 @@ cdef list update_reduce(factory, list eqns): _field = factory._field one = _field.one() _ks = factory._ks + #Update reduction params + factory._nnz = factory._get_known_nonz() + factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) cdef dict _kp = factory._kp cdef ETuple _nnz = factory._nnz @@ -404,40 +404,6 @@ cdef list compute_gb(factory, tuple args): res.append(t) return collect_eqns(res) -cpdef update_child_fmats(factory, tuple data_tup): - r""" - One-to-all communication used to update FMatrix object after each triangular - elim step. We must update the algorithm's state values. These are: - ``_fvars``, ``_solved``, ``_ks``, ``_var_degs``, ``_nnz``, and ``_kp``. - - TESTS:: - - sage: f = FMatrix(FusionRing("A1",3)) - sage: f._reset_solver_state() - sage: f.get_orthogonality_constraints(output=False) - sage: from multiprocessing import cpu_count, Pool, set_start_method, shared_memory - sage: pool = f.get_worker_pool() - sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) - sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup - sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: f.mp_thresh = 0 - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} - sage: f._triangular_elim(worker_pool=pool) # indirect doctest - Elimination epoch completed... 10 eqns remain in ideal basis - Elimination epoch completed... 0 eqns remain in ideal basis - sage: f.ideal_basis - [] - """ - #factory object is assumed global before forking used to create the Pool object, - #so each child has a global fmats variable. So it's enough to update that object - # factory._fvars = data_tup[0] - factory._nnz = factory._get_known_nonz() - factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) - - #Wait this process isn't used again - sleep(0.5) - ################ ### Reducers ### ################ @@ -464,7 +430,6 @@ cdef dict mappers = { "get_reduced_pentagons": get_reduced_pentagons, "update_reduce": update_reduce, "compute_gb": compute_gb, - "update_child_fmats": update_child_fmats, "pent_verify": pent_verify } @@ -496,18 +461,18 @@ cpdef executor(tuple params): sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor sage: fmats = FMatrix(FusionRing("A1",3)) sage: fmats._reset_solver_state() - sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0,1,True)) sage: len(executor(params)) == 63 True sage: fmats = FMatrix(FusionRing("E6",1)) sage: fmats._reset_solver_state() - sage: params = (('get_reduced_hexagons', id(fmats)), (0,1)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0,1,False)) sage: len(executor(params)) == 6 True """ (fn_name, fmats_id), args = params #Construct a reference to global FMatrix object in this worker's memory - fmats_obj = ctypes.cast(fmats_id, ctypes.py_object).value + fmats_obj = cast(fmats_id, py_object).value #Bind module method to FMatrix object in worker process, and call the method return mappers[fn_name](fmats_obj,args) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 0a768b40851..5d9987f5a9c 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -10,13 +10,11 @@ cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cpdef bint tup_fixes_sq(tuple eq_tup) -# cdef dict subs_squares(dict eq_dict, dict known_sq) cdef dict subs_squares(dict eq_dict, known_sq) cpdef dict compute_known_powers(max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) cpdef tup_to_univ_poly(tuple eq_tup, univ_poly_ring) cpdef tuple poly_tup_sortkey(tuple eq_tup) -# cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, dict known_sq, NumberFieldElement_absolute one) cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, known_sq, NumberFieldElement_absolute one) cdef tuple _flatten_coeffs(tuple eq_tup) cpdef tuple _unflatten_coeffs(field, tuple eq_tup) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 519b5023e94..e4144eb5bfb 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -359,7 +359,6 @@ cpdef inline bint tup_fixes_sq(tuple eq_tup): ### Simplification ### ###################### -# cdef dict subs_squares(dict eq_dict, dict known_sq): cdef dict subs_squares(dict eq_dict, known_sq): r""" Substitute for known squares into a given polynomial. diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 52efb52a8a4..f72b963c0c9 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -1,25 +1,32 @@ from cysignals.memory cimport sig_malloc +cimport numpy as np from sage.rings.polynomial.polydict cimport ETuple +from sage.rings.number_field.number_field_base cimport NumberField from multiprocessing import shared_memory -import numpy as np from sage.misc.cachefunc import cached_method +import numpy as np class KSHandler(): + # cdef field + # cdef np.ndarray ks_dat + # cdef public shm + def __init__(self, factory, name=None): self.field = factory._field - self.index = 0 - n = len(factory._fvars) + n = factory._poly_ring.ngens() d = self.field.degree() ks_t = np.dtype([ ('known', 'bool', (1,)), - ('nums','i4',(d,)), - ('denoms','u4',(d,)) + ('nums','i8',(d,)), + ('denoms','u8',(d,)) ]) if name is None: self.shm = shared_memory.SharedMemory(create=True,size=n*ks_t.itemsize) self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) - self.reset() + self.ks_dat['known'] = np.zeros((n,1),dtype='bool') + self.ks_dat['nums'] = np.zeros((n,d),dtype='i4') + self.ks_dat['denoms'] = np.ones((n,d),dtype='u4') else: self.shm = shared_memory.SharedMemory(name=name) self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) @@ -28,62 +35,116 @@ class KSHandler(): def __getitem__(self, idx): if not self.ks_dat['known'][idx]: raise KeyError('Index {} does not correspond to a known square'.format(idx)) + cdef unsigned int i, d cdef list rat = list() - for i in range(self.field.degree()): + d = self.field.degree() + for i in range(d): rat.append(self.ks_dat['nums'][idx][i] / self.ks_dat['denoms'][idx][i]) return self.field(rat) def __setitem__(self, idx, rhs): self.ks_dat['known'][idx] = True - cdef int i + cdef unsigned int i for i in range(len(rhs)): - self.ks_dat['nums'][idx][i] = -rhs[i].numerator() + self.ks_dat['nums'][idx][i] = rhs[i].numerator() self.ks_dat['denoms'][idx][i] = rhs[i].denominator() - def __iter__(self): - self.index = 0 - return self - - def __next__(self): - if self.index == self.ks_dat.size: - raise StopIteration - - #Skip indices that are not known - while not self.ks_dat['known'][self.index]: - self.index += 1 - if self.index == self.ks_dat.size: - raise StopIteration - - self.index += 1 - return self[self.index-1] - def __contains__(self, idx): return self.ks_dat['known'][idx] def __len__(self): - return sum(self.ks_dat['known']) + """ + Compute the number of known squares. + + """ + return self.ks_dat['known'].sum() def __eq__(self, other): + """ + Test for equality. + """ return np.all(self.ks_dat == other.ks_dat) def items(self): - for v in self: - yield self.index-1, v - - def reset(self): - n = self.ks_dat.size - d = self.field.degree() - self.__getitem__.clear_cache() - self.ks_dat['known'] = np.zeros((n,1),dtype='bool') - self.ks_dat['nums'] = np.zeros((n,d),dtype='i4') - self.ks_dat['denoms'] = np.ones((n,d),dtype='u4') + cdef unsigned int i + for i in range(self.ks_dat.size): + if self.ks_dat['known'][i]: + yield i, self[i] -class FvarsHandler(): - # cdef dict sext_to_idx - # cdef int ngens - # cdef field, fvars, fvars_t, shm +cdef class FvarsHandler(): + cdef dict sext_to_idx, obj_cache + cdef unsigned int ngens + cdef fvars_t + cdef NumberField field + cdef public np.ndarray fvars + cdef public shm def __init__(self, factory, name=None, max_terms=20): + """ + Return a shared memory backed dict-like structure to manage the + ``_fvars`` attribute of an F-matrix factory object. + + This structure implements a representation of the F-symbols dictionary + using a structured NumPy array backed by a contiguous shared memory + object. + + The monomial data is stored in the ``exp_data`` structure. Monomial + exponent data is stored contiguously and ``ticks`` are used to + indicate different monomials. + + Coefficient data is stored in the ``coeff_nums`` and ``coeff_denom`` + arrays. The ``coeff_denom`` array stores the value + ``d = coeff.denominator()`` for each cyclotomic coefficient. The + ``coeff_nums`` array stores the values + ``c.numerator() * d for c in coeff._coefficients()``, the abridged + list representation of the cyclotomic coefficient ``coeff``. + + Each entry also has a boolean ``modified`` attribute, indicating + whether it has been modified by the parent process. Entry retrieval + is cached in each process, so each process must check whether + entries have been modified before attempting retrieval. + + The parent process should construct this object without a + ``name`` attribute. Children processes use the ``name`` attribute, + accessed via ``self.shm.name`` to attach to the shared memory block. + + INPUT: + + - ``factory`` -- an F-matrix factory object. + - ``name`` -- the name of a shared memory object + (used by child processes for attaching). + - ``max_terms`` -- maximum number of terms in each entry. Since + we use contiguous C-style memory blocks, the size of the block + must be known in advance. + + .. NOTE:: + + To properly dispose of shared memory resources, + ``self.shm.unlink()`` must be called before exiting. + + .. WARNING:: + + The current data structure supports up to 2**16-1 entries, + each with each monomial in each entry having at most 254 + nonzero terms. On average, each of the ``max_terms`` monomials + can have at most 50 terms. + + EXAMPLES:: + + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: #Create shared data structure + sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) + creating variables fx1..fx8 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 + sage: fvars = FvarsHandler(f) + sage: #In the same shell or in a different shell, attach to fvars + sage: fvars2 = FvarsHandler(f, name=fvars.shm.name) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: fvars[f2, f1, f2, f2, f0, f0] = poly_to_tup(fx5**5) + sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) + fx5^5 + sage: fvars.shm.unlink() + """ n = len(factory._fvars) d = factory._field.degree() self.obj_cache = dict() @@ -104,11 +165,32 @@ class FvarsHandler(): self.shm = shared_memory.SharedMemory(name=name) self.fvars = np.ndarray((n,),dtype=self.fvars_t,buffer=self.shm.buf) - #Given a tuple of labels and a tuple of (ETuple, cyc_coeff) pairs, insert into - #shared dictionary - #WARNING: current data structure supports up to 2**16-1 entries, - #each with each monomial in each entry having at most 254 nonzero terms. def __setitem__(self, sextuple, fvar): + """ + Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, + create or overwrite an entry in the shared data structure + corresponding to the given sextuple. + + EXAMPLES:: + + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) + creating variables fx1..fx27 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: fvars = FvarsHandler(f) + sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) + sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) + sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) + sage: s, t, r = (f3, f2, f1, f2, f1, f3), (f3, f2, f3, f0, f1, f1), (f3, f3, f3, f1, f2, f2) + sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10 + True + sage: f._tup_to_fpoly(fvars[t]) == 0 + True + sage: f._tup_to_fpoly(fvars[r]) == -1/19 + True + sage: fvars.shm.unlink() + """ cdef int cum, denom, i, j, k, idx cdef ETuple exp idx = self.sext_to_idx[sextuple] @@ -132,10 +214,42 @@ class FvarsHandler(): i += 1 self.fvars['modified'][idx] = True - #Retrieve a record by unflattening and constructing relevant Python objects - #Only the parent is allowed to modify the shared _fvars object, so to implement caching - #we must tag modified objects and delete cache if present before retrieval def __getitem__(self, sextuple): + """ + Retrieve a record from the shared memory data structure by + unflattening its representation and constructing relevant Python + objects. + + This method returns a tuple of ``(ETuple, coeff)`` pairs, + where ``coeff`` is an element of ``self.field``. + + EXAMPLES:: + + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) + creating variables fx1..fx27 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: fvars = FvarsHandler(f) + sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) + sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) + sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) + sage: s, t, r = (f3, f2, f1, f2, f1, f3), (f3, f2, f3, f0, f1, f1), (f3, f3, f3, f1, f2, f2) + sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10 + True + sage: f._tup_to_fpoly(fvars[t]) == 0 + True + sage: f._tup_to_fpoly(fvars[r]) == -1/19 + True + sage: fvars.shm.unlink() + + .. NOTE:: + + This method implements caching. Only the parent process is allowed + to modify the shared fvars structure. Each process builds its own + cache, so each process must update its cache before retrieving a + modified entry, tagged via its ``modified`` property. + """ if not sextuple in self.sext_to_idx: raise KeyError('Invalid sextuple {}'.format(sextuple)) cdef int idx = self.sext_to_idx[sextuple] @@ -145,8 +259,9 @@ class FvarsHandler(): else: return self.obj_cache[idx] cdef ETuple e = ETuple({}, self.ngens) - cdef int cum, d, i, j + cdef unsigned int cum, d, i, j, nnz cdef list poly_tup = list() + cdef tuple ret cum = 0 for i in range(np.count_nonzero(self.fvars['ticks'][idx])): #Construct new ETuple for each monomial @@ -164,16 +279,30 @@ class FvarsHandler(): cyc_coeff = self.field([num / d for num in self.fvars['coeff_nums'][idx][i]]) poly_tup.append((exp, cyc_coeff)) - cdef tuple ret = tuple(poly_tup) + ret = tuple(poly_tup) self.obj_cache[idx] = ret return ret - def __len__(self): - return self.fvars.size + def items(self): + """ + Iterates through key-value pairs in the data structure as if it + were a Python dict. - def __repr__(self): - return str({sextuple: self[sextuple] for sextuple in self.sext_to_idx}) + As in a Python dict, the key-value pairs are yielded in no particular + order. - def items(self): + EXAMPLES:: + + sage: f = FMatrix(FusionRing("G2", 1), inject_variables=True) + creating variables fx1..fx5 + Defining fx0, fx1, fx2, fx3, fx4 + sage: p = f.get_worker_pool() + sage: for sextuple, fvar in f._shared_fvars.items(): + ....: if sextuple == (f1, f1, f1, f1, f1, f1): + ....: f._tup_to_fpoly(fvar) + ....: + fx4 + sage: f.shutdown_worker_pool(p) + """ for sextuple in self.sext_to_idx: yield sextuple, self[sextuple] From b430001d6ff5b91619c9f6534ad7115697b33db8 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 30 Apr 2021 16:53:01 -0400 Subject: [PATCH 061/414] added copyright message to shm_managers and fixed doctest --- .../combinat/root_system/shm_managers.pyx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index f72b963c0c9..253dc8e466a 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -1,3 +1,13 @@ +""" +Shared memory managers for F-symbol attributes +""" +# **************************************************************************** +# Copyright (C) 2021 Guillermo Aboumrad +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + from cysignals.memory cimport sig_malloc cimport numpy as np from sage.rings.polynomial.polydict cimport ETuple @@ -227,15 +237,15 @@ cdef class FvarsHandler(): sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) - creating variables fx1..fx27 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: f = FMatrix(FusionRing("B7", 1), inject_variables=True) + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: fvars = FvarsHandler(f) - sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) - sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) - sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) - sage: s, t, r = (f3, f2, f1, f2, f1, f3), (f3, f2, f3, f0, f1, f1), (f3, f3, f3, f1, f2, f2) - sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10 + sage: fvars[(f1, f2, f1, f2, f2, f2)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10) + sage: fvars[f2, f2, f2, f2, f0, f0] = poly_to_tup(f._poly_ring.zero()) + sage: fvars[f2, f1, f2, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) + sage: s, t, r = (f1, f2, f1, f2, f2, f2), (f2, f2, f2, f2, f0, f0), (f2, f1, f2, f1, f2, f2) + sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10 True sage: f._tup_to_fpoly(fvars[t]) == 0 True From 34cb4f63a391dbefc319ba529a06f34d567cb8f9 Mon Sep 17 00:00:00 2001 From: Daniel Bump Date: Fri, 30 Apr 2021 15:29:12 -0700 Subject: [PATCH 062/414] minor doctest tweaks --- src/sage/combinat/root_system/f_matrix.py | 12 ++++++------ src/sage/combinat/root_system/fusion_ring.py | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index ca61dab5793..efa16b5abb8 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -748,13 +748,13 @@ def get_non_cyclotomic_roots(self): True sage: f.get_non_cyclotomic_roots() [] - sage: f = FMatrix(FusionRing("F4",1)) + sage: f = FMatrix(FusionRing("G2",1)) sage: f.find_orthogonal_solution(verbose=False) sage: f.field() == f.FR().field() False sage: phi = f.get_qqbar_embedding() sage: [phi(r).n() for r in f.get_non_cyclotomic_roots()] - [-0.786151377757423 + 1.73579267033929e-59*I] + [-0.786151377757423 - 8.92806368517581e-31*I] When ``self.field()`` is a ``NumberField``, one may use :meth:`get_qqbar_embedding` to embed the resulting values into @@ -774,11 +774,11 @@ def get_qqbar_embedding(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("F4",1), fusion_label="f", inject_variables=True) + sage: f = FMatrix(FusionRing("G2",1), fusion_label="g", inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: f.find_orthogonal_solution() - Computing F-symbols for The Fusion Ring of Type F4 and level 1 with Integer Ring coefficients with 5 variables... + Computing F-symbols for The Fusion Ring of Type G2 and level 1 with Integer Ring coefficients with 5 variables... Set up 10 hex and orthogonality constraints... Partitioned 10 equations into 2 components of size: [4, 1] @@ -792,8 +792,8 @@ def get_qqbar_embedding(self): [1] Computing appropriate NumberField... sage: phi = f.get_qqbar_embedding() - sage: phi(f.fmat(f1,f1,f1,f1,f1,f1)).n() - -0.618033988749895 + 3.63089268571980e-21*I + sage: phi(f.fmat(g1,g1,g1,g1,g1,g1)).n() + -0.618033988749895 + 1.46674215951686e-29*I """ return self._qqbar_embedding diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index b602904d1aa..db546190cb7 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -216,8 +216,10 @@ class FusionRing(WeylCharacterRing): sage: I.fusion_labels(["i0","p","s"],inject_variables=True) sage: b = I.basis().list(); b [i0, p, s] - sage: [[x*y for x in b] for y in b] - [[i0, p, s], [p, i0, s], [s, s, i0 + p]] + sage: Matrix([[x*y for x in b] for y in b]) # long time (.93s) + [ i0 p s] + [ p i0 s] + [ s s i0 + p] sage: [x.twist() for x in b] [0, 1, 1/8] sage: [x.ribbon() for x in b] From c67dfc20af0a6b396e76d46e117ba2951b289645 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 30 Apr 2021 18:38:02 -0400 Subject: [PATCH 063/414] indentation warning block --- src/sage/combinat/root_system/f_matrix.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index efa16b5abb8..10335cd49b3 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1310,16 +1310,16 @@ def get_worker_pool(self,processes=None): .. WARNING:: - This method is needed to initialize the worker pool using the - necessary shared memory resources. Simply using the - ``multiprocessing.Pool`` constructor will not work with our class - methods. + This method is needed to initialize the worker pool using the + necessary shared memory resources. Simply using the + ``multiprocessing.Pool`` constructor will not work with our class + methods. .. WARNING:: - Failure to call :meth:`shutdown_worker_pool` may result in a memory - leak, since shared memory resources outlive the process that created - them. + Failure to call :meth:`shutdown_worker_pool` may result in a memory + leak, since shared memory resources outlive the process that created + them. """ try: set_start_method('fork') From 4959ff7c7f0be31d023de48eeb2f86eef854a63d Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sat, 1 May 2021 16:49:16 -0400 Subject: [PATCH 064/414] KSHandler now a cdef class, some minor touch ups --- src/sage/combinat/root_system/f_matrix.py | 10 +-- .../fast_parallel_fusion_ring_braid_repn.pyx | 6 ++ .../combinat/root_system/poly_tup_engine.pyx | 3 +- .../combinat/root_system/shm_managers.pyx | 79 ++++++++++++------- 4 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 10335cd49b3..f48901a8190 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1158,7 +1158,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(verbose=False) - sage: f._update_reduction_params(children_need_update=False) + sage: f._update_reduction_params() sage: f._checkpoint(do_chkpt=True,status=2) Checkpoint 2 reached! sage: del f @@ -1189,7 +1189,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(verbose=False) - sage: f._update_reduction_params(children_need_update=False) + sage: f._update_reduction_params() sage: f.get_defining_equations('pentagons',output=False) sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f._triangular_elim(verbose=False) @@ -1233,7 +1233,7 @@ def _restore_state(self,filename): sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} sage: f._triangular_elim(verbose=False) - sage: f._update_reduction_params(children_need_update=False) + sage: f._update_reduction_params() sage: fvars = f._fvars sage: ib = f.ideal_basis sage: solved = f._solved @@ -1580,7 +1580,7 @@ def _tup_to_fpoly(self,eq_tup): """ return _tup_to_poly(eq_tup,parent=self._poly_ring) - def _update_reduction_params(self,eqns=None,worker_pool=None,children_need_update=False): + def _update_reduction_params(self,eqns=None): r""" Update reduction parameters that are solver state attributes. @@ -1652,7 +1652,7 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): _backward_subs(self) #Compute new reduction params, send to child processes if any, and update eqns - self._update_reduction_params(eqns=eqns,worker_pool=worker_pool,children_need_update=len(eqns)>self.mp_thresh) + self._update_reduction_params(eqns=eqns) # n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) # eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] if worker_pool is not None and len(eqns) > self.mp_thresh: diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index a685dc1d48f..a9664c79139 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -77,6 +77,9 @@ cdef odd_one_out_ij_cache = dict() cdef mid_sig_ij_cache = dict() cdef cached_mid_sig_ij(fusion_ring,row,col,a,b): + """ + Cached version of :meth:`mid_sig_ij`. + """ if (row,col,a,b) in mid_sig_ij_cache: return mid_sig_ij_cache[row,col,a,b] entry = mid_sig_ij(fusion_ring,row,col,a,b) @@ -84,6 +87,9 @@ cdef cached_mid_sig_ij(fusion_ring,row,col,a,b): return entry cdef cached_odd_one_out_ij(fusion_ring,xi,xj,a,b): + """ + Cached version of :meth:`odd_one_out_ij`. + """ if (xi,xj,a,b) in odd_one_out_ij_cache: return odd_one_out_ij_cache[xi,xj,a,b] entry = odd_one_out_ij(fusion_ring,xi,xj,a,b) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index e4144eb5bfb..ad3245a287f 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -383,7 +383,8 @@ cdef dict subs_squares(dict eq_dict, known_sq): new_e = dict() for idx, power in exp.sparse_iter(): if idx in known_sq: - coeff *= known_sq[idx] ** (power // 2) + # coeff *= known_sq[idx] ** (power // 2) + coeff *= pow(known_sq[idx], power // 2) #New power is 1 if power is odd if power & True: new_e[idx] = 1 diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 253dc8e466a..e617881b02b 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -8,21 +8,25 @@ Shared memory managers for F-symbol attributes # https://www.gnu.org/licenses/ # **************************************************************************** +cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np from sage.rings.polynomial.polydict cimport ETuple from sage.rings.number_field.number_field_base cimport NumberField +# from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute from multiprocessing import shared_memory from sage.misc.cachefunc import cached_method import numpy as np -class KSHandler(): - # cdef field - # cdef np.ndarray ks_dat - # cdef public shm +cdef class KSHandler(): + cdef list obj_cache + cdef np.ndarray ks_dat + cdef NumberField field + cdef public shm def __init__(self, factory, name=None): + cdef int n, d self.field = factory._field n = factory._poly_ring.ngens() d = self.field.degree() @@ -31,33 +35,45 @@ class KSHandler(): ('nums','i8',(d,)), ('denoms','u8',(d,)) ]) + self.obj_cache = [None]*n if name is None: self.shm = shared_memory.SharedMemory(create=True,size=n*ks_t.itemsize) self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) self.ks_dat['known'] = np.zeros((n,1),dtype='bool') - self.ks_dat['nums'] = np.zeros((n,d),dtype='i4') - self.ks_dat['denoms'] = np.ones((n,d),dtype='u4') + self.ks_dat['nums'] = np.zeros((n,d),dtype='i8') + self.ks_dat['denoms'] = np.ones((n,d),dtype='u8') else: self.shm = shared_memory.SharedMemory(name=name) self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) - @cached_method + @cython.nonecheck(False) + @cython.wraparound(False) def __getitem__(self, idx): if not self.ks_dat['known'][idx]: raise KeyError('Index {} does not correspond to a known square'.format(idx)) + if self.obj_cache[idx] is not None: + return self.obj_cache[idx] cdef unsigned int i, d cdef list rat = list() d = self.field.degree() for i in range(d): - rat.append(self.ks_dat['nums'][idx][i] / self.ks_dat['denoms'][idx][i]) - return self.field(rat) + rat.append(self.ks_dat['nums'][idx,i] / self.ks_dat['denoms'][idx,i]) + cyc_coeff = self.field(rat) + self.obj_cache[idx] = cyc_coeff + return cyc_coeff + @cython.nonecheck(False) + @cython.wraparound(False) def __setitem__(self, idx, rhs): self.ks_dat['known'][idx] = True cdef unsigned int i + cdef long long num + cdef unsigned long long denom for i in range(len(rhs)): - self.ks_dat['nums'][idx][i] = rhs[i].numerator() - self.ks_dat['denoms'][idx][i] = rhs[i].denominator() + num = (rhs[i].numerator()) + denom = (rhs[i].denominator()) + self.ks_dat['nums'][idx,i] = num + self.ks_dat['denoms'][idx,i] = denom def __contains__(self, idx): return self.ks_dat['known'][idx] @@ -155,8 +171,7 @@ cdef class FvarsHandler(): fx5^5 sage: fvars.shm.unlink() """ - n = len(factory._fvars) - d = factory._field.degree() + cdef int d = factory._field.degree() self.obj_cache = dict() self.fvars_t = np.dtype([ ('modified','bool',(1,)), @@ -169,12 +184,14 @@ cdef class FvarsHandler(): self.ngens = factory._poly_ring.ngens() self.field = factory._field if name is None: - self.shm = shared_memory.SharedMemory(create=True,size=n*self.fvars_t.itemsize) - self.fvars = np.ndarray((n,),dtype=self.fvars_t,buffer=self.shm.buf) + self.shm = shared_memory.SharedMemory(create=True,size=self.ngens*self.fvars_t.itemsize) + self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) else: self.shm = shared_memory.SharedMemory(name=name) - self.fvars = np.ndarray((n,),dtype=self.fvars_t,buffer=self.shm.buf) + self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) + @cython.nonecheck(False) + @cython.wraparound(False) def __setitem__(self, sextuple, fvar): """ Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, @@ -201,7 +218,9 @@ cdef class FvarsHandler(): True sage: fvars.shm.unlink() """ - cdef int cum, denom, i, j, k, idx + cdef unsigned int cum, i, j, k, idx + cdef unsigned long denom + cdef long c cdef ETuple exp idx = self.sext_to_idx[sextuple] #Clear entry before inserting @@ -211,19 +230,22 @@ cdef class FvarsHandler(): for exp, coeff in fvar: #Handle constant coefficient if exp._nonzero > 0: - self.fvars['ticks'][idx][i] = exp._nonzero + self.fvars['ticks'][idx,i] = exp._nonzero else: - self.fvars['ticks'][idx][i] = -1 + self.fvars['ticks'][idx,i] = -1 for j in range(2*exp._nonzero): - self.fvars['exp_data'][idx][cum] = exp._data[j] + self.fvars['exp_data'][idx,cum] = exp._data[j] cum += 1 denom = coeff.denominator() - self.fvars['coeff_denom'][idx][i] = denom - for k, c in enumerate(coeff._coefficients()): - self.fvars['coeff_nums'][idx][i,k] = c * denom + self.fvars['coeff_denom'][idx,i] = denom + for k, r in enumerate(coeff._coefficients()): + c = r * denom + self.fvars['coeff_nums'][idx,i,k] = c i += 1 self.fvars['modified'][idx] = True + @cython.nonecheck(False) + @cython.wraparound(False) def __getitem__(self, sextuple): """ Retrieve a record from the shared memory data structure by @@ -269,7 +291,8 @@ cdef class FvarsHandler(): else: return self.obj_cache[idx] cdef ETuple e = ETuple({}, self.ngens) - cdef unsigned int cum, d, i, j, nnz + cdef unsigned int cum, i, j, nnz + cdef unsigned long d cdef list poly_tup = list() cdef tuple ret cum = 0 @@ -277,16 +300,16 @@ cdef class FvarsHandler(): #Construct new ETuple for each monomial exp = e._new() #Handle constant coeff - nnz = self.fvars['ticks'][idx][i] if self.fvars['ticks'][idx][i] < 255 else 0 + nnz = self.fvars['ticks'][idx,i] if self.fvars['ticks'][idx,i] < 255 else 0 exp._nonzero = nnz exp._data = sig_malloc(sizeof(int)*nnz*2) for j in range(2*nnz): - exp._data[j] = self.fvars['exp_data'][idx][cum] + exp._data[j] = self.fvars['exp_data'][idx,cum] cum += 1 #Construct cyclotomic field coefficient - d = self.fvars['coeff_denom'][idx][i] - cyc_coeff = self.field([num / d for num in self.fvars['coeff_nums'][idx][i]]) + d = self.fvars['coeff_denom'][idx,i] + cyc_coeff = self.field([num / d for num in self.fvars['coeff_nums'][idx,i]]) poly_tup.append((exp, cyc_coeff)) ret = tuple(poly_tup) From f5ae50158920e83d7d54aeac1aaa5c94dc0acbeb Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 2 May 2021 11:59:34 -0400 Subject: [PATCH 065/414] new constructor and pickling capability for KSHandler --- src/sage/combinat/root_system/f_matrix.py | 22 ++++--- .../combinat/root_system/poly_tup_engine.pxd | 2 + .../combinat/root_system/shm_managers.pyx | 57 +++++++++++++++++-- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index f48901a8190..0101471d3d7 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -394,9 +394,11 @@ def _reset_solver_state(self): self._chkpt_status = -1 self.clear_vars() self.clear_equations() - self._var_degs = [0]*self._poly_ring.ngens() + n = self._poly_ring.ngens() + self._var_degs = [0]*n self._kp = dict() - self._ks = dict() + # self._ks = dict() + self._ks = KSHandler(n,self._field) self._nnz = self._get_known_nonz() #Clear relevant caches @@ -1332,12 +1334,13 @@ def get_worker_pool(self,processes=None): s_name = self._solved.shm.name self._var_degs = shared_memory.ShareableList(self._var_degs) vd_name = self._var_degs.shm.name - ks = KSHandler(self) - for idx, val in self._ks.items(): - if not isinstance(val, tuple): - val = val._coefficients() - ks[idx] = val - self._ks = ks + n = self._poly_ring.ngens() + self._ks = KSHandler(n,self._field,use_mp=True,init_data=self._ks) + # for idx, val in self._ks.items(): + # if not isinstance(val, tuple): + # val = val._coefficients() + # ks[idx] = val + # self._ks = ks ks_names = self._ks.shm.name self._shared_fvars = FvarsHandler(self) for sextuple, fvar in self._fvars.items(): @@ -1356,8 +1359,9 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): fmats_obj = cast(fmats_id, py_object).value fmats_obj._solved = shared_memory.ShareableList(name=solved_name) fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) - fmats_obj._ks = KSHandler(fmats_obj,name=ks_names) fmats_obj._fvars = FvarsHandler(fmats_obj,name=fvar_names) + n = fmats_obj._poly_ring.ngens() + fmats_obj._ks = KSHandler(n,fmats_obj._field,name=ks_names) pool = Pool(processes=n,initializer=init,initargs=args) return pool diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 5d9987f5a9c..349f9e7be43 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -2,6 +2,8 @@ from sage.rings.number_field.number_field_element cimport NumberFieldElement_abs from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple +# from sage.combinat.root_system.shm_managers cimport KSHandler + cpdef tuple poly_to_tup(MPolynomial_libsingular poly) cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index e617881b02b..06829a46d83 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -25,10 +25,10 @@ cdef class KSHandler(): cdef NumberField field cdef public shm - def __init__(self, factory, name=None): + def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): cdef int n, d - self.field = factory._field - n = factory._poly_ring.ngens() + self.field = field + n = n_slots d = self.field.degree() ks_t = np.dtype([ ('known', 'bool', (1,)), @@ -37,14 +37,20 @@ cdef class KSHandler(): ]) self.obj_cache = [None]*n if name is None: - self.shm = shared_memory.SharedMemory(create=True,size=n*ks_t.itemsize) - self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) + if use_mp: + self.shm = shared_memory.SharedMemory(create=True,size=n*ks_t.itemsize) + self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) + else: + self.ks_dat = np.ndarray((n,),dtype=ks_t) self.ks_dat['known'] = np.zeros((n,1),dtype='bool') self.ks_dat['nums'] = np.zeros((n,d),dtype='i8') self.ks_dat['denoms'] = np.ones((n,d),dtype='u8') else: self.shm = shared_memory.SharedMemory(name=name) self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) + #Populate initializer data + for idx, sq in init_data.items(): + self[idx] = sq @cython.nonecheck(False) @cython.wraparound(False) @@ -69,6 +75,8 @@ cdef class KSHandler(): cdef unsigned int i cdef long long num cdef unsigned long long denom + if not isinstance(rhs, list): + rhs = rhs._coefficients() for i in range(len(rhs)): num = (rhs[i].numerator()) denom = (rhs[i].denominator()) @@ -89,7 +97,28 @@ cdef class KSHandler(): """ Test for equality. """ - return np.all(self.ks_dat == other.ks_dat) + ret = True + for idx, sq in self.items(): + ret &= other[idx] == sq + # return np.all(self.ks_dat == other.ks_dat) + return ret + + def __reduce__(self): + """ + Provide pickling / unpickling support for ``self.`` + + TESTS:: + + sage: f = FMatrix(FusionRing("A3",1)) + sage: f._reset_solver_state() + sage: loads(dumps(f._ks)) == f._ks + True + sage: f.find_orthogonal_solution(verbose=False) #long time + sage: loads(dumps(f._ks)) == f._ks + True + """ + d = {i: sq for i, sq in self.items()} + return make_KSHandler, (self.ks_dat.size,self.field,d) def items(self): cdef unsigned int i @@ -97,6 +126,22 @@ cdef class KSHandler(): if self.ks_dat['known'][i]: yield i, self[i] +def make_KSHandler(n_slots,field,init_data): + """ + Provide pickling / unpickling support for ``self.`` + + TESTS:: + + sage: f = FMatrix(FusionRing("B4",1)) + sage: f._reset_solver_state() + sage: loads(dumps(f._ks)) == f._ks + True + sage: f.find_orthogonal_solution(verbose=False) #long time + sage: loads(dumps(f._ks)) == f._ks + True + """ + return KSHandler(n_slots,field,init_data=init_data) + cdef class FvarsHandler(): cdef dict sext_to_idx, obj_cache cdef unsigned int ngens From c27b3d703ff07771b847dd4ffd01227be9865cbd Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 2 May 2021 13:37:14 -0400 Subject: [PATCH 066/414] removed _get_known_sq method, cdef attributes for KSHandler --- src/sage/combinat/root_system/f_matrix.py | 52 +----------- .../combinat/root_system/poly_tup_engine.pxd | 9 +- .../combinat/root_system/poly_tup_engine.pyx | 31 ++----- .../combinat/root_system/shm_managers.pxd | 22 +++++ .../combinat/root_system/shm_managers.pyx | 85 +++++++++++++------ 5 files changed, 94 insertions(+), 105 deletions(-) create mode 100644 src/sage/combinat/root_system/shm_managers.pxd diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 0101471d3d7..ca2bd9323c3 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -35,7 +35,6 @@ poly_to_tup, _tup_to_poly, tup_to_univ_poly, _unflatten_coeffs, poly_tup_sortkey, - tup_fixes_sq, resize, ) from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler @@ -397,7 +396,6 @@ def _reset_solver_state(self): n = self._poly_ring.ngens() self._var_degs = [0]*n self._kp = dict() - # self._ks = dict() self._ks = KSHandler(n,self._field) self._nnz = self._get_known_nonz() @@ -908,49 +906,6 @@ def _get_known_vals(self): """ return {i: self._fvars[s] for i, s in self._idx_to_sextuple.items() if self._solved[i]} - def _get_known_sq(self,eqns=None): - r""" - Update ```self``'s dictionary of known squares. Keys are variable - indices and corresponding values are the squares. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("B5",1)) - sage: f._reset_solver_state() - sage: len(f._ks) == 0 - True - sage: f.get_orthogonality_constraints() - [fx0^2 - 1, - fx1^2 - 1, - fx2^2 - 1, - fx3^2 - 1, - fx4^2 - 1, - fx5^2 - 1, - fx6^2 - 1, - fx7^2 - 1, - fx8^2 - 1, - fx9^2 - 1, - fx10^2 + fx12^2 - 1, - fx10*fx11 + fx12*fx13, - fx10*fx11 + fx12*fx13, - fx11^2 + fx13^2 - 1] - sage: f.get_orthogonality_constraints(output=False) - sage: f._get_known_sq() - sage: len(f._ks) == 10 - True - """ - if eqns is None: - eqns = self.ideal_basis - for eq_tup in eqns: - if tup_fixes_sq(eq_tup): - #Handle mp and single process cases - if isinstance(self._ks, dict): - rhs = -self._field(list(eq_tup[-1][1])) - else: - rhs = [-v for v in eq_tup[-1][1]] - self._ks[variables(eq_tup)[0]] = rhs - - def _get_known_nonz(self): r""" Construct an ETuple indicating positions of known nonzero variables. @@ -1336,11 +1291,6 @@ def get_worker_pool(self,processes=None): vd_name = self._var_degs.shm.name n = self._poly_ring.ngens() self._ks = KSHandler(n,self._field,use_mp=True,init_data=self._ks) - # for idx, val in self._ks.items(): - # if not isinstance(val, tuple): - # val = val._coefficients() - # ks[idx] = val - # self._ks = ks ks_names = self._ks.shm.name self._shared_fvars = FvarsHandler(self) for sextuple, fvar in self._fvars.items(): @@ -1607,7 +1557,7 @@ def _update_reduction_params(self,eqns=None): """ if eqns is None: eqns = self.ideal_basis - self._get_known_sq(eqns) + self._ks.update(eqns) degs = get_variables_degrees(eqns) if degs: for i, d in enumerate(degs): diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 349f9e7be43..5526a71ff22 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -1,9 +1,8 @@ +from sage.combinat.root_system.shm_managers cimport KSHandler from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple -# from sage.combinat.root_system.shm_managers cimport KSHandler - cpdef tuple poly_to_tup(MPolynomial_libsingular poly) cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) @@ -11,8 +10,10 @@ cpdef list get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) -cpdef bint tup_fixes_sq(tuple eq_tup) -cdef dict subs_squares(dict eq_dict, known_sq) +# cpdef bint tup_fixes_sq(tuple eq_tup) +cdef bint tup_fixes_sq(tuple eq_tup) +# cdef dict subs_squares(dict eq_dict, known_sq) +cdef dict subs_squares(dict eq_dict, KSHandler known_sq) cpdef dict compute_known_powers(max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) cpdef tup_to_univ_poly(tuple eq_tup, univ_poly_ring) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index ad3245a287f..8db9e9f017e 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -319,34 +319,16 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): new_tup.append((exp, coeff_map(coeff))) return tuple(new_tup) -cpdef inline bint tup_fixes_sq(tuple eq_tup): +cdef inline bint tup_fixes_sq(tuple eq_tup): r""" Determine if given equation fixes the square of a variable. An equation fixes the sq of a variable if it is of the form `a*x^2 + c` for *nonzero* constants `a`, `c`. - - EXAMPLES:: - - sage: from sage.combinat.root_system.poly_tup_engine import tup_fixes_sq - sage: R. = PolynomialRing(QQ) - sage: sq_fixer = 3*z**2 - 1/8 - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: tup_fixes_sq(poly_to_tup(sq_fixer)) - True - sage: tup_fixes_sq(poly_to_tup(y**3 + 2)) - False - sage: tup_fixes_sq(poly_to_tup(x*y + 2)) - False - sage: tup_fixes_sq(poly_to_tup(x**2 + y**2 + 2)) - False """ - #Make this faster by combining two conditions into one... don't create temp variables - # return len(eq_tup) == 2 and len(variables(eq_tup)) == 1 and eq_tup[0][0].nonzero_values() == [2] - # return len(eq_tup) == 2 and eq_tup[0][0].emax(eq_tup[1][0]).nonzero_values() == [2] if len(eq_tup) != 2: return False - #In order to access _attributes, we must cdef ETuple + #To access _attributes, we must cdef ETuple cdef ETuple lm = eq_tup[0][0] if lm._nonzero != 1 or lm._data[1] != 2: return False @@ -359,7 +341,8 @@ cpdef inline bint tup_fixes_sq(tuple eq_tup): ### Simplification ### ###################### -cdef dict subs_squares(dict eq_dict, known_sq): +# cdef dict subs_squares(dict eq_dict, known_sq): +cdef dict subs_squares(dict eq_dict, KSHandler known_sq): r""" Substitute for known squares into a given polynomial. @@ -382,9 +365,11 @@ cdef dict subs_squares(dict eq_dict, known_sq): for exp, coeff in eq_dict.items(): new_e = dict() for idx, power in exp.sparse_iter(): - if idx in known_sq: + # if idx in known_sq: + if known_sq.contains(idx): # coeff *= known_sq[idx] ** (power // 2) - coeff *= pow(known_sq[idx], power // 2) + # coeff *= pow(known_sq[idx], power // 2) + coeff *= pow(known_sq.get(idx), power // 2) #New power is 1 if power is odd if power & True: new_e[idx] = 1 diff --git a/src/sage/combinat/root_system/shm_managers.pxd b/src/sage/combinat/root_system/shm_managers.pxd new file mode 100644 index 00000000000..e3d4f44ad4a --- /dev/null +++ b/src/sage/combinat/root_system/shm_managers.pxd @@ -0,0 +1,22 @@ +cimport numpy as np +import numpy as np +from sage.rings.number_field.number_field_base cimport NumberField + +cdef class KSHandler: + cdef list obj_cache + cdef np.ndarray ks_dat + cdef NumberField field + cdef public shm + + cdef bint contains(self, int idx) + cdef get(self, int idx) + cdef setitem(self, int idx, rhs) + cpdef update(self, list eqns) + +cdef class FvarsHandler: + cdef dict sext_to_idx, obj_cache + cdef unsigned int ngens + cdef fvars_t + cdef NumberField field + cdef public np.ndarray fvars + cdef public shm diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 06829a46d83..e0eebf3f50d 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -11,20 +11,14 @@ Shared memory managers for F-symbol attributes cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np +from sage.combinat.root_system.poly_tup_engine cimport tup_fixes_sq, variables from sage.rings.polynomial.polydict cimport ETuple -from sage.rings.number_field.number_field_base cimport NumberField # from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute from multiprocessing import shared_memory -from sage.misc.cachefunc import cached_method import numpy as np -cdef class KSHandler(): - cdef list obj_cache - cdef np.ndarray ks_dat - cdef NumberField field - cdef public shm - +cdef class KSHandler: def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): cdef int n, d self.field = field @@ -50,11 +44,11 @@ cdef class KSHandler(): self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) #Populate initializer data for idx, sq in init_data.items(): - self[idx] = sq + self.setitem(idx,sq) @cython.nonecheck(False) @cython.wraparound(False) - def __getitem__(self, idx): + cdef get(self, int idx): if not self.ks_dat['known'][idx]: raise KeyError('Index {} does not correspond to a known square'.format(idx)) if self.obj_cache[idx] is not None: @@ -68,13 +62,58 @@ cdef class KSHandler(): self.obj_cache[idx] = cyc_coeff return cyc_coeff + cpdef update(self, list eqns): + r""" + Update ```self``'s ``shared_memory``-backed dictionary of known + squares. Keys are variable indices and corresponding values + are the squares. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("B5",1)) + sage: f._reset_solver_state() + sage: len(f._ks) == 0 + True + sage: f.get_orthogonality_constraints() + [fx0^2 - 1, + fx1^2 - 1, + fx2^2 - 1, + fx3^2 - 1, + fx4^2 - 1, + fx5^2 - 1, + fx6^2 - 1, + fx7^2 - 1, + fx8^2 - 1, + fx9^2 - 1, + fx10^2 + fx12^2 - 1, + fx10*fx11 + fx12*fx13, + fx10*fx11 + fx12*fx13, + fx11^2 + fx13^2 - 1] + sage: f.get_orthogonality_constraints(output=False) + sage: f._ks.update(f.ideal_basis) + sage: len(f._ks) == 10 + True + """ + cdef unsigned int i, idx + cdef ETuple lm + cdef list rhs + cdef tuple eq_tup + for i in range(len(eqns)): + eq_tup = eqns[i] + if tup_fixes_sq(eq_tup): + rhs = [-v for v in eq_tup[-1][1]] + #eq_tup is guaranteed univariate, so we extract variable idx from lm + lm = eq_tup[0][0] + idx = lm._data[0] + self.setitem(idx, rhs) + @cython.nonecheck(False) @cython.wraparound(False) - def __setitem__(self, idx, rhs): - self.ks_dat['known'][idx] = True + cdef setitem(self, int idx, rhs): cdef unsigned int i cdef long long num cdef unsigned long long denom + self.ks_dat['known'][idx] = True if not isinstance(rhs, list): rhs = rhs._coefficients() for i in range(len(rhs)): @@ -83,8 +122,8 @@ cdef class KSHandler(): self.ks_dat['nums'][idx,i] = num self.ks_dat['denoms'][idx,i] = denom - def __contains__(self, idx): - return self.ks_dat['known'][idx] + cdef bint contains(self, int idx): + return self.ks_dat[idx]['known'] def __len__(self): """ @@ -93,14 +132,13 @@ cdef class KSHandler(): """ return self.ks_dat['known'].sum() - def __eq__(self, other): + def __eq__(self, KSHandler other): """ Test for equality. """ ret = True for idx, sq in self.items(): - ret &= other[idx] == sq - # return np.all(self.ks_dat == other.ks_dat) + ret &= other.get(idx) == sq return ret def __reduce__(self): @@ -124,11 +162,11 @@ cdef class KSHandler(): cdef unsigned int i for i in range(self.ks_dat.size): if self.ks_dat['known'][i]: - yield i, self[i] + yield i, self.get(i) def make_KSHandler(n_slots,field,init_data): """ - Provide pickling / unpickling support for ``self.`` + Provide pickling / unpickling support for :class:`KSHandler`. TESTS:: @@ -142,14 +180,7 @@ def make_KSHandler(n_slots,field,init_data): """ return KSHandler(n_slots,field,init_data=init_data) -cdef class FvarsHandler(): - cdef dict sext_to_idx, obj_cache - cdef unsigned int ngens - cdef fvars_t - cdef NumberField field - cdef public np.ndarray fvars - cdef public shm - +cdef class FvarsHandler: def __init__(self, factory, name=None, max_terms=20): """ Return a shared memory backed dict-like structure to manage the From f65d76595f49dd8f0ae7e6f9f12a4ff2fa0146a4 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 2 May 2021 15:38:54 -0400 Subject: [PATCH 067/414] documented KSHandler --- src/sage/combinat/root_system/f_matrix.py | 13 +- .../combinat/root_system/shm_managers.pxd | 3 +- .../combinat/root_system/shm_managers.pyx | 152 ++++++++++++++++-- 3 files changed, 146 insertions(+), 22 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index ca2bd9323c3..1920e53db76 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -376,8 +376,9 @@ def _reset_solver_state(self): True sage: len(f.ideal_basis) == 0 True - sage: len(f._ks) == 0 - True + sage: for k, v in f._ks.items(): + ....: k + ....: sage: len(f._nnz.nonzero_positions()) == 1 True sage: all(len(x.q_dimension.cache) == 0 for x in f.FR().basis()) @@ -1323,10 +1324,10 @@ def shutdown_worker_pool(self,pool): .. WARNING:: - Failure to call this method after using :meth:`get_worker_pool` - to create a process pool may result in a memory - leak, since shared memory resources outlive the process that created - them. + Failure to call this method after using :meth:`get_worker_pool` + to create a process pool may result in a memory + leak, since shared memory resources outlive the process that created + them. EXAMPLES:: diff --git a/src/sage/combinat/root_system/shm_managers.pxd b/src/sage/combinat/root_system/shm_managers.pxd index e3d4f44ad4a..be13623646f 100644 --- a/src/sage/combinat/root_system/shm_managers.pxd +++ b/src/sage/combinat/root_system/shm_managers.pxd @@ -1,6 +1,7 @@ cimport numpy as np import numpy as np from sage.rings.number_field.number_field_base cimport NumberField +from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute cdef class KSHandler: cdef list obj_cache @@ -9,7 +10,7 @@ cdef class KSHandler: cdef public shm cdef bint contains(self, int idx) - cdef get(self, int idx) + cdef NumberFieldElement_absolute get(self, int idx) cdef setitem(self, int idx, rhs) cpdef update(self, list eqns) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index e0eebf3f50d..35fd8cf3f73 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -11,14 +11,76 @@ Shared memory managers for F-symbol attributes cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np -from sage.combinat.root_system.poly_tup_engine cimport tup_fixes_sq, variables +from sage.combinat.root_system.poly_tup_engine cimport tup_fixes_sq from sage.rings.polynomial.polydict cimport ETuple -# from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute from multiprocessing import shared_memory import numpy as np cdef class KSHandler: + """ + Return a shared memory backed dict-like structure to manage the + ``_ks`` attribute of an F-matrix factory object. + + This structure implements a representation of the known squares dictionary + using a structured NumPy array backed by a contiguous shared memory + object. + + The structure mimics a dictionary of ``(idx, known_sq)`` pairs. Each + integer index corresponds to a variable and each ``known_sq`` is an + element of the F-matrix factory's base cyclotomic field. + + Each cyclotomic coefficient is stored as a list of numerators and a + list of denominators representing the rational coefficients. The + structured array also maintains ``known`` attribute that indicates + whether the structure contains an entry corresponding to the given index. + + The parent process should construct this object without a + ``name`` attribute. Children processes use the ``name`` attribute, + accessed via ``self.shm.name`` to attach to the shared memory block. + + INPUT: + + - ``n_slots`` -- The total number of F-symbols. + - ``field`` -- F-matrix factory's base cyclotomic field. + - ``use_mp`` -- a boolean indicating whether to construct a shared + memory block to back ``self``. + - ``name`` -- the name of a shared memory object + (used by child processes for attaching). + - ``init_data`` -- a dictionary or :class:`KSHandler` object containing + known squares for initialization, e.g. from a solver checkpoint. + + .. NOTE:: + + To properly dispose of shared memory resources, + ``self.shm.unlink()`` must be called before exiting. + + .. WARNING:: + + This structure does *not* cannot modify an entry that + has already been set. + + EXAMPLES:: + + sage: from sage.combinat.root_system.shm_managers import KSHandler + sage: #Create shared data structure + sage: f = FMatrix(FusionRing("A1",2), inject_variables=True) + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + sage: n = f._poly_ring.ngens() + sage: ks = KSHandler(n,f._field,use_mp=True) + sage: #In the same shell or in a different shell, attach to fvars + sage: ks2 = KSHandler(n,f._field,name=ks.shm.name) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] + sage: ks.update([poly_to_tup(p) for p in eqns]) + sage: for idx, sq in ks.items(): + ....: print("Index: {}, square: {}".format(idx, sq)) + ....: + Index: 1, square: 4 + Index: 3, square: -zeta32^4 + 1/19*zeta32^2 + sage: ks.shm.unlink() + """ def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): cdef int n, d self.field = field @@ -48,13 +110,18 @@ cdef class KSHandler: @cython.nonecheck(False) @cython.wraparound(False) - cdef get(self, int idx): + cdef NumberFieldElement_absolute get(self, int idx): + """ + Retrieve the known square corresponding to the given index, + if it exists. + """ if not self.ks_dat['known'][idx]: raise KeyError('Index {} does not correspond to a known square'.format(idx)) if self.obj_cache[idx] is not None: return self.obj_cache[idx] cdef unsigned int i, d cdef list rat = list() + cdef NumberFieldElement_absolute cyc_coeff d = self.field.degree() for i in range(d): rat.append(self.ks_dat['nums'][idx,i] / self.ks_dat['denoms'][idx,i]) @@ -72,8 +139,9 @@ cdef class KSHandler: sage: f = FMatrix(FusionRing("B5",1)) sage: f._reset_solver_state() - sage: len(f._ks) == 0 - True + sage: for idx, sq in f._ks.items(): + ....: k + ....: sage: f.get_orthogonality_constraints() [fx0^2 - 1, fx1^2 - 1, @@ -91,8 +159,23 @@ cdef class KSHandler: fx11^2 + fx13^2 - 1] sage: f.get_orthogonality_constraints(output=False) sage: f._ks.update(f.ideal_basis) - sage: len(f._ks) == 10 - True + sage: for idx, sq in f._ks.items(): + ....: print(idx, "-->", sq) + ....: + 0 --> 1 + 1 --> 1 + 2 --> 1 + 3 --> 1 + 4 --> 1 + 5 --> 1 + 6 --> 1 + 7 --> 1 + 8 --> 1 + 9 --> 1 + + .. WARNING:: + + This method assumes every polynomial in ``eqns`` is *monic*. """ cdef unsigned int i, idx cdef ETuple lm @@ -110,6 +193,12 @@ cdef class KSHandler: @cython.nonecheck(False) @cython.wraparound(False) cdef setitem(self, int idx, rhs): + """ + Create an entry corresponding to the given index. + + The ``rhs`` parameter may be a cyclotomic coefficient or its + list/tuple representation. + """ cdef unsigned int i cdef long long num cdef unsigned long long denom @@ -123,18 +212,29 @@ cdef class KSHandler: self.ks_dat['denoms'][idx,i] = denom cdef bint contains(self, int idx): - return self.ks_dat[idx]['known'] - - def __len__(self): """ - Compute the number of known squares. - + Determine whether ``self`` contains entry corresponding to given + ``idx``. """ - return self.ks_dat['known'].sum() + return self.ks_dat[idx]['known'] def __eq__(self, KSHandler other): """ Test for equality. + + TESTS:: + + sage: f = FMatrix(FusionRing("C2",2)) + sage: f._reset_solver_state() + sage: f.get_orthogonality_constraints(output=False) + sage: from sage.combinat.root_system.shm_managers import KSHandler + sage: n = f._poly_ring.ngens() + sage: ks = KSHandler(n,f._field,use_mp=True,init_data=f._ks) + sage: #In the same shell or in a different one, attach to shared memory handler + sage: k2 = KSHandler(n,f._field,name=ks.shm.name) + sage: ks == k2 + True + sage: ks.shm.unlink() """ ret = True for idx, sq in self.items(): @@ -159,6 +259,27 @@ cdef class KSHandler: return make_KSHandler, (self.ks_dat.size,self.field,d) def items(self): + """ + Iterate through existing entries using Python dict-style syntax. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A3",1)) + sage: f._reset_solver_state() + sage: f.get_orthogonality_constraints(output=False) + sage: f._ks.update(f.ideal_basis) + sage: for idx, sq in f._ks.items(): + ....: print("Index: {}, sq: {}".format(idx,sq)) + ....: + Index: 0, sq: 1 + Index: 1, sq: 1 + Index: 2, sq: 1 + Index: 3, sq: 1 + Index: 4, sq: 1 + ... + Index: 25, sq: 1 + Index: 26, sq: 1 + """ cdef unsigned int i for i in range(self.ks_dat.size): if self.ks_dat['known'][i]: @@ -172,10 +293,10 @@ def make_KSHandler(n_slots,field,init_data): sage: f = FMatrix(FusionRing("B4",1)) sage: f._reset_solver_state() - sage: loads(dumps(f._ks)) == f._ks + sage: loads(dumps(f._ks)) == f._ks #indirect doctest True sage: f.find_orthogonal_solution(verbose=False) #long time - sage: loads(dumps(f._ks)) == f._ks + sage: loads(dumps(f._ks)) == f._ks #indirect doctest True """ return KSHandler(n_slots,field,init_data=init_data) @@ -298,6 +419,7 @@ cdef class FvarsHandler: cdef unsigned long denom cdef long c cdef ETuple exp + cdef NumberFieldElement_absolute coeff idx = self.sext_to_idx[sextuple] #Clear entry before inserting self.fvars[idx] = np.zeros((1,), dtype=self.fvars_t) From 651fe2a7633f9c92dd0a1eaa12811f2a3389f174 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 2 May 2021 16:50:54 -0400 Subject: [PATCH 068/414] pickling support for FvarsHandler... required for checkpointing --- src/sage/combinat/root_system/f_matrix.py | 14 ++--- .../combinat/root_system/poly_tup_engine.pxd | 2 - .../combinat/root_system/shm_managers.pyx | 62 ++++++++++++++++--- 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 1920e53db76..0b4ee2f5d3f 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1293,12 +1293,12 @@ def get_worker_pool(self,processes=None): n = self._poly_ring.ngens() self._ks = KSHandler(n,self._field,use_mp=True,init_data=self._ks) ks_names = self._ks.shm.name - self._shared_fvars = FvarsHandler(self) - for sextuple, fvar in self._fvars.items(): - if self._chkpt_status < 0: - self._shared_fvars[sextuple] = poly_to_tup(fvar) - else: - self._shared_fvars[sextuple] = fvar + self._shared_fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,init_data=self._fvars) + # for sextuple, fvar in self._fvars.items(): + # if self._chkpt_status < 0: + # self._shared_fvars[sextuple] = poly_to_tup(fvar) + # else: + # self._shared_fvars[sextuple] = fvar fvar_names = self._shared_fvars.shm.name #Initialize worker pool processes args = (id(self), s_name, vd_name, ks_names, fvar_names) @@ -1310,8 +1310,8 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): fmats_obj = cast(fmats_id, py_object).value fmats_obj._solved = shared_memory.ShareableList(name=solved_name) fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) - fmats_obj._fvars = FvarsHandler(fmats_obj,name=fvar_names) n = fmats_obj._poly_ring.ngens() + fmats_obj._fvars = FvarsHandler(n,fmats_obj._field,fmats_obj._idx_to_sextuple,name=fvar_names) fmats_obj._ks = KSHandler(n,fmats_obj._field,name=ks_names) pool = Pool(processes=n,initializer=init,initargs=args) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 5526a71ff22..7c889bce661 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -10,9 +10,7 @@ cpdef list get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) -# cpdef bint tup_fixes_sq(tuple eq_tup) cdef bint tup_fixes_sq(tuple eq_tup) -# cdef dict subs_squares(dict eq_dict, known_sq) cdef dict subs_squares(dict eq_dict, KSHandler known_sq) cpdef dict compute_known_powers(max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 35fd8cf3f73..e1c8c553b2d 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -11,7 +11,7 @@ Shared memory managers for F-symbol attributes cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np -from sage.combinat.root_system.poly_tup_engine cimport tup_fixes_sq +from sage.combinat.root_system.poly_tup_engine cimport poly_to_tup, tup_fixes_sq from sage.rings.polynomial.polydict cimport ETuple from multiprocessing import shared_memory @@ -302,7 +302,7 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self, factory, name=None, max_terms=20): + def __init__(self,n_slots,field,idx_to_sextuple,init_data={},name=None,max_terms=20): """ Return a shared memory backed dict-like structure to manage the ``_fvars`` attribute of an F-matrix factory object. @@ -359,16 +359,17 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 - sage: fvars = FvarsHandler(f) + sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple) sage: #In the same shell or in a different shell, attach to fvars - sage: fvars2 = FvarsHandler(f, name=fvars.shm.name) + sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name) sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: fvars[f2, f1, f2, f2, f0, f0] = poly_to_tup(fx5**5) sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) fx5^5 sage: fvars.shm.unlink() """ - cdef int d = factory._field.degree() + self.field = field + cdef int d = self.field.degree() self.obj_cache = dict() self.fvars_t = np.dtype([ ('modified','bool',(1,)), @@ -377,15 +378,19 @@ cdef class FvarsHandler: ('coeff_nums','i4',(max_terms,d)), ('coeff_denom','u4',(max_terms,)) ]) - self.sext_to_idx = {s: i for i, s in factory._idx_to_sextuple.items()} - self.ngens = factory._poly_ring.ngens() - self.field = factory._field + self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} + self.ngens = n_slots if name is None: self.shm = shared_memory.SharedMemory(create=True,size=self.ngens*self.fvars_t.itemsize) self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) else: self.shm = shared_memory.SharedMemory(name=name) self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) + #Populate with initialziation data + for sextuple, fvar in init_data.items(): + if not isinstance(fvar, tuple): + fvar = poly_to_tup(fvar) + self[sextuple] = fvar @cython.nonecheck(False) @cython.wraparound(False) @@ -402,7 +407,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 - sage: fvars = FvarsHandler(f) + sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple) sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) @@ -460,7 +465,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("B7", 1), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - sage: fvars = FvarsHandler(f) + sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple) sage: fvars[(f1, f2, f1, f2, f2, f2)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10) sage: fvars[f2, f2, f2, f2, f0, f0] = poly_to_tup(f._poly_ring.zero()) sage: fvars[f2, f1, f2, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) @@ -514,6 +519,26 @@ cdef class FvarsHandler: self.obj_cache[idx] = ret return ret + def __reduce__(self): + """ + Provide pickling / unpickling support for ``self.`` + + TESTS:: + + sage: f = FMatrix(FusionRing("F4",1)) + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: for s, fvar in loads(dumps(fvars)).items(): + ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) + ....: + sage: fvars.shm.unlink() + """ + n = self.fvars.size + idx_map = {i: s for s, i in self.sext_to_idx.items()} + d = {s: fvar for s, fvar in self.items()} + return make_FvarsHandler, (n,self.field,idx_map,d) + def items(self): """ Iterates through key-value pairs in the data structure as if it @@ -537,3 +562,20 @@ cdef class FvarsHandler: """ for sextuple in self.sext_to_idx: yield sextuple, self[sextuple] + +def make_FvarsHandler(n,field,idx_map,init_data): + """ + Provide pickling / unpickling support for :class:`FvarsHandler`. + + TESTS:: + + sage: f = FMatrix(FusionRing("G2",1)) + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: for s, fvar in loads(dumps(fvars)).items(): + ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) + ....: + sage: fvars.shm.unlink() + """ + return FvarsHandler(n,field,idx_map,init_data=init_data) From ba0d435a354e19035ed95cf8f77cbc68c92473b6 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 2 May 2021 20:58:14 -0400 Subject: [PATCH 069/414] new pool usage syntax, added pool attribute to FMatrix --- src/sage/combinat/root_system/f_matrix.py | 148 +++++++++--------- .../fast_parallel_fmats_methods.pxd | 2 + .../fast_parallel_fmats_methods.pyx | 9 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 4 +- .../combinat/root_system/shm_managers.pyx | 111 ++++++------- 5 files changed, 141 insertions(+), 133 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 0b4ee2f5d3f..fabc75fc3ed 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -299,6 +299,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab #Multiprocessing attributes self.mp_thresh = 10000 + self.pool = None ####################### ### Class utilities ### @@ -424,6 +425,7 @@ def _update_poly_ring_base_field(self,field): new_poly_ring = self._poly_ring.change_ring(field) nvars = self._poly_ring.ngens() #Do some appropriate conversions + self._singles = [new_poly_ring.gen(self._var_to_idx[fx]) for fx in self._singles] self._var_to_idx = {new_poly_ring.gen(i): i for i in range(nvars)} self._var_to_sextuple = {new_poly_ring.gen(i): self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars)} self._poly_ring = new_poly_ring @@ -1240,13 +1242,15 @@ def _restore_state(self,filename): ### MapReduce ### ################# - def get_worker_pool(self,processes=None): + def start_worker_pool(self,processes=None): """ - Get an initialized worker pool for parallel processing, + Initialize a ``multiprocessing`` worker pool for parallel processing, which may be used e.g. to set up defining equations using :meth:`get_defining_equations`. - This pool may be reused time and again. + This method creates the attribute ``self.pool``, and the worker + pool may be used time and again. Upon initialization, each process + in the pool is attached to the necessary shared memory resources. When you are done using the worker pool, use :meth:`shutdown_worker_pool` to close the pool and properly dispose @@ -1255,23 +1259,23 @@ def get_worker_pool(self,processes=None): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",1)) - sage: pool = f.get_worker_pool() - sage: he = f.get_defining_equations('hexagons',worker_pool=pool) + sage: f.start_worker_pool() + sage: he = f.get_defining_equations('hexagons') sage: sorted(he) [fx0 - 1, fx2*fx3 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx4^2 + (zeta60^6)*fx4, fx1*fx3 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx3*fx4 + (zeta60^14 - zeta60^4)*fx3, fx1*fx2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx4 + (zeta60^14 - zeta60^4)*fx2, fx1^2 + (zeta60^14 + zeta60^12 - zeta60^6 - zeta60^4 + 1)*fx2*fx3 + (-zeta60^12)*fx1] - sage: pe = f.get_defining_equations('pentagons',worker_pool=pool) - sage: f.shutdown_worker_pool(pool) + sage: pe = f.get_defining_equations('pentagons') + sage: f.shutdown_worker_pool() .. WARNING:: This method is needed to initialize the worker pool using the necessary shared memory resources. Simply using the - ``multiprocessing.Pool`` constructor will not work with our class - methods. + ``multiprocessing.Pool`` constructor will not work with our + class methods. .. WARNING:: @@ -1294,11 +1298,6 @@ def get_worker_pool(self,processes=None): self._ks = KSHandler(n,self._field,use_mp=True,init_data=self._ks) ks_names = self._ks.shm.name self._shared_fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,init_data=self._fvars) - # for sextuple, fvar in self._fvars.items(): - # if self._chkpt_status < 0: - # self._shared_fvars[sextuple] = poly_to_tup(fvar) - # else: - # self._shared_fvars[sextuple] = fvar fvar_names = self._shared_fvars.shm.name #Initialize worker pool processes args = (id(self), s_name, vd_name, ks_names, fvar_names) @@ -1314,17 +1313,16 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): fmats_obj._fvars = FvarsHandler(n,fmats_obj._field,fmats_obj._idx_to_sextuple,name=fvar_names) fmats_obj._ks = KSHandler(n,fmats_obj._field,name=ks_names) - pool = Pool(processes=n,initializer=init,initargs=args) - return pool + self.pool = Pool(processes=n,initializer=init,initargs=args) - def shutdown_worker_pool(self,pool): + def shutdown_worker_pool(self): """ Shutdown the given worker pool and dispose of shared memory resources - created when the pool was set up using :meth:`get_worker_pool`. + created when the pool was set up using :meth:`start_worker_pool`. .. WARNING:: - Failure to call this method after using :meth:`get_worker_pool` + Failure to call this method after using :meth:`start_worker_pool` to create a process pool may result in a memory leak, since shared memory resources outlive the process that created them. @@ -1332,16 +1330,17 @@ def shutdown_worker_pool(self,pool): EXAMPLES:: sage: f = FMatrix(FusionRing("A1",3)) - sage: pool = f.get_worker_pool() - sage: he = f.get_defining_equations('hexagons', worker_pool=pool) - sage: f.shutdown_worker_pool(pool) + sage: f.start_worker_pool() + sage: he = f.get_defining_equations('hexagons') + sage: f.shutdown_worker_pool() """ - pool.close() - self._solved.shm.unlink() - self._var_degs.shm.unlink() - self._ks.shm.unlink() - self._shared_fvars.shm.unlink() - del self.__dict__['_shared_fvars'] + if self.pool is not None: + self.pool.close() + self._solved.shm.unlink() + self._var_degs.shm.unlink() + self._ks.shm.unlink() + self._shared_fvars.shm.unlink() + del self.__dict__['_shared_fvars'] def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): r""" @@ -1369,11 +1368,11 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t sage: f._reset_solver_state() sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1,False)])) 11 - sage: pool = f.get_worker_pool() - sage: mp_params = [(i,pool._processes,True) for i in range(pool._processes)] - sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=pool,chunksize=1,mp_thresh=0)) + sage: f.start_worker_pool() + sage: mp_params = [(i,f.pool._processes,True) for i in range(f.pool._processes)] + sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=f.pool,chunksize=1,mp_thresh=0)) 33 - sage: f.shutdown_worker_pool(pool) + sage: f.shutdown_worker_pool() """ if mp_thresh is None: mp_thresh = self.mp_thresh @@ -1449,7 +1448,7 @@ def get_orthogonality_constraints(self,output=True): return eqns self.ideal_basis.extend([poly_to_tup(eq) for eq in eqns]) - def get_defining_equations(self,option,worker_pool=None,output=True): + def get_defining_equations(self,option,output=True): r""" Get the equations defining the ideal generated by the hexagon or pentagon relations. @@ -1464,14 +1463,10 @@ def get_defining_equations(self,option,worker_pool=None,output=True): * ``'pentagons'`` - get equations imposed on the F-matrix by the pentagon relations in the definition of a monoidal category - - ``worker_pool`` -- (default: ``None``) a ``Pool`` object of the - Python ``multiprocessing`` module - - ``output`` -- (default: ``True``) a boolean indicating whether results should be returned, where the equations will be polynomials. - Otherwise, the constraints are appended to ``self.ideal_basis``. - They are stored in the internal tuple representation. The + Constraints are stored in the internal tuple representation. The ``output=False`` option is meant only for internal use by the F-matrix solver. When computing the hexagon equations with the ``output=False`` option, the initial state of the F-symbols is used. @@ -1494,12 +1489,18 @@ def get_defining_equations(self,option,worker_pool=None,output=True): sage: pe = f.get_defining_equations('pentagons') sage: len(pe) 33 + + .. NOTE:: + + To set up the defining equations using parallel processing, + use :meth:`start_worker_pool` to initialize multiple processes + *before* calling this method. """ if not hasattr(self, '_nnz'): self._reset_solver_state() - n_proc = worker_pool._processes if worker_pool is not None else 1 + n_proc = self.pool._processes if self.pool is not None else 1 params = [(child_id, n_proc, output) for child_id in range(n_proc)] - eqns = self._map_triv_reduce('get_reduced_'+option,params,worker_pool=worker_pool,chunksize=1,mp_thresh=0) + eqns = self._map_triv_reduce('get_reduced_'+option,params,worker_pool=self.pool,chunksize=1,mp_thresh=0) if output: F = self._field for i, eq_tup in enumerate(eqns): @@ -1528,10 +1529,11 @@ def _tup_to_fpoly(self,eq_tup): sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f = FMatrix(FusionRing("C3",1)) - sage: pool = f.get_worker_pool() - sage: he = f.get_defining_equations('hexagons',pool) + sage: f.start_worker_pool() + sage: he = f.get_defining_equations('hexagons') sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True + sage: f.shutdown_worker_pool() """ return _tup_to_poly(eq_tup,parent=self._poly_ring) @@ -1544,17 +1546,17 @@ def _update_reduction_params(self,eqns=None): sage: f = FMatrix(FusionRing("A1",3)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: pool = f.get_worker_pool() - sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) - sage: f.ideal_basis = f._par_graph_gb(worker_pool=pool,verbose=False) + sage: f.start_worker_pool() + sage: f.get_defining_equations('hexagons',output=False) + sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 sage: f._fvars = f._shared_fvars - sage: f._triangular_elim(worker_pool=pool,verbose=False) # indirect doctest + sage: f._triangular_elim(verbose=False) # indirect doctest sage: f.ideal_basis [] - sage: f.shutdown_worker_pool(pool) + sage: f.shutdown_worker_pool() """ if eqns is None: eqns = self.ideal_basis @@ -1569,7 +1571,7 @@ def _update_reduction_params(self,eqns=None): self._nnz = self._get_known_nonz() self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) - def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): + def _triangular_elim(self,eqns=None,verbose=True): r""" Perform triangular elimination of linear terms in two-term equations until no such terms exist. @@ -1599,7 +1601,7 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): ret = False while True: #Reset modification cache - if worker_pool is not None: + if self.pool is not None: self._fvars.fvars['modified'][:] = False linear_terms_exist = _solve_for_linear_terms(self,eqns) if not linear_terms_exist: @@ -1610,15 +1612,15 @@ def _triangular_elim(self,eqns=None,worker_pool=None,verbose=True): self._update_reduction_params(eqns=eqns) # n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) # eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] - if worker_pool is not None and len(eqns) > self.mp_thresh: - n = worker_pool._processes + if self.pool is not None and len(eqns) > self.mp_thresh: + n = self.pool._processes chunks = [[] for i in range(n)] for i, eq_tup in enumerate(eqns): chunks[i%n].append(eq_tup) eqns = chunks else: eqns = [eqns] - eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=worker_pool) + eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=self.pool) eqns.sort(key=poly_tup_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) @@ -1683,13 +1685,13 @@ def equations_graph(self,eqns=None): if not eqns: return G #Eqns could be a list of poly objects or poly tuples stored in internal repn - if type(eqns[0]) == tuple: + if isinstance(eqns[0], tuple): G.add_vertices([x for eq_tup in eqns for x in variables(eq_tup)]) else: G.add_vertices([x for eq in eqns for x in eq.variables()]) for eq in eqns: #Eqns could be a list of poly objects or poly tuples stored in internal repn - if type(eq) == tuple: + if isinstance(eq, tuple): s = [v for v in variables(eq)] else: s = [v for v in eq.variables()] @@ -1744,7 +1746,7 @@ def _partition_eqns(self,eqns=None,verbose=True): print(graph.connected_components_sizes()) return partition - def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose=True): + def _par_graph_gb(self,eqns=None,term_order="degrevlex",verbose=True): r""" Compute a Groebner basis for a list of equations partitioned according to their corresponding graph. @@ -1763,9 +1765,9 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose sage: f = FMatrix(FusionRing("F4",1)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: pool = f.get_worker_pool() - sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) - sage: gb = f._par_graph_gb(worker_pool=pool) + sage: f.start_worker_pool() + sage: f.get_defining_equations('hexagons',output=False) + sage: gb = f._par_graph_gb() Partitioned 10 equations into 2 components of size: [4, 1] sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs @@ -1776,6 +1778,7 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose fx1 + (zeta80^24 - zeta80^16), fx0 - 1, fx3^2 + (zeta80^24 - zeta80^16)] + sage: f.shutdown_worker_pool() """ if eqns is None: eqns = self.ideal_basis small_comps = list() @@ -1787,7 +1790,7 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose # for i in range(nmax+1): # vars_by_size.append(self.get_fvars_by_size(i)) - for comp, comp_eqns in self._partition_eqns(verbose=verbose).items():#self._partition_eqns(graph,verbose=verbose).items(): + for comp, comp_eqns in self._partition_eqns(verbose=verbose).items(): #Check if component is too large to process if len(comp) > 60: # fmat_size = 0 @@ -1800,7 +1803,7 @@ def _par_graph_gb(self,worker_pool=None,eqns=None,term_order="degrevlex",verbose else: small_comps.append(comp_eqns) input_iter = zip_longest(small_comps,[],fillvalue=term_order) - small_comp_gb = self._map_triv_reduce('compute_gb',input_iter,worker_pool=worker_pool,chunksize=1,mp_thresh=50) + small_comp_gb = self._map_triv_reduce('compute_gb',input_iter,worker_pool=self.pool,chunksize=1,mp_thresh=50) ret = small_comp_gb + temp_eqns return ret @@ -1818,8 +1821,8 @@ def _get_component_variety(self,var,eqns): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",2)) - sage: pool = f.get_worker_pool() - sage: f.get_defining_equations('hexagons',worker_pool=pool,output=False) # long time + sage: f.start_worker_pool() + sage: f.get_defining_equations('hexagons',output=False) # long time sage: partition = f._partition_eqns() # long time Partitioned 327 equations into 35 components of size: [27, 27, 27, 24, 24, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, @@ -1829,6 +1832,7 @@ def _get_component_variety(self,var,eqns): sage: eqns = partition[c] + [poly_to_tup(f._poly_ring.gen(216)-1)] # long time sage: f._get_component_variety(c,eqns) # long time [{216: -1, 292: -1, 319: 1}] + sage: f.shutdown_worker_pool() """ #Define smaller poly ring in component vars R = PolynomialRing(self._FR.field(), len(var), 'a', order='lex') @@ -1937,7 +1941,8 @@ def _get_explicit_solution(self,eqns=None,verbose=True): for fx, rhs in self._ks.items(): if not self._solved[fx]: lt = (ETuple({fx : 2},n), one) - eqns.append((lt, (ETuple({},n), -self._field(list(rhs))))) + # eqns.append((lt, (ETuple({},n), -self._field(list(rhs))))) + eqns.append(((lt, (ETuple({},n), -rhs)))) eqns_partition = self._partition_eqns(verbose=verbose) F = self._field @@ -2115,14 +2120,14 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if self._chkpt_status > 5: return #max(cpu_count()-1,1) - pool = self.get_worker_pool() if use_mp else None + if use_mp: self.start_worker_pool() if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, self._poly_ring.ngens())) if self._chkpt_status < 1: #Set up hexagon equations and orthogonality constraints self.get_orthogonality_constraints(output=False) - self.get_defining_equations('hexagons',worker_pool=pool,output=False) + self.get_defining_equations('hexagons',output=False) #Report progress if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) @@ -2134,9 +2139,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if self._chkpt_status < 2: #Set up equations graph. Find GB for each component in parallel. Eliminate variables - self.ideal_basis = self._par_graph_gb(worker_pool=pool,verbose=verbose) + self.ideal_basis = self._par_graph_gb(verbose=verbose) self.ideal_basis.sort(key=poly_tup_sortkey) - self._triangular_elim(worker_pool=pool,verbose=verbose) + self._triangular_elim(verbose=verbose) #Report progress if verbose: print("Hex elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) @@ -2145,7 +2150,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if self._chkpt_status < 3: #Set up pentagon equations in parallel - self.get_defining_equations('pentagons',worker_pool=pool,output=False) + self.get_defining_equations('pentagons',output=False) self.ideal_basis.sort(key=poly_tup_sortkey) #Report progress if verbose: @@ -2155,7 +2160,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Simplify and eliminate variables if self._chkpt_status < 4: - self._triangular_elim(worker_pool=pool,verbose=verbose) + self._triangular_elim(verbose=verbose) #Report progress if verbose: print("Pent elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) @@ -2179,8 +2184,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self._chkpt_status = 7 self.clear_equations() #Close worker pool and destroy shared resources - if use_mp: - self.shutdown_worker_pool(pool) + self.shutdown_worker_pool() if checkpoint: remove("fmatrix_solver_checkpoint_"+self.get_fr_str()+".pickle") if save_results: @@ -2211,9 +2215,7 @@ def _fix_gauge(self, algorithm=""): adding equation... fx18 - 1 adding equation... fx21 - 1 """ - # while len(self._solved) < len(self._poly_ring.gens()): while sum(1 for v in self._solved if not v) > 0: - # while self._solved.sum() < self._solved.size: #Get a variable that has not been fixed #In ascending index order, for consistent results for i, var in enumerate(self._poly_ring.gens()): diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index f54fa2ff174..f4f12e9c0d5 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -1,2 +1,4 @@ cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) +cpdef _backward_subs(factory) cpdef executor(tuple params) +cpdef _solve_for_linear_terms(factory, list eqns=*) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 02d2ebc799f..c42b95616fe 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -18,6 +18,7 @@ from sage.combinat.root_system.poly_tup_engine cimport ( has_appropriate_linear_term, resize ) +from sage.combinat.root_system.shm_managers cimport KSHandler, FvarsHandler from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple @@ -26,14 +27,13 @@ from ctypes import cast, py_object from itertools import product from multiprocessing import shared_memory from sage.rings.ideal import Ideal -from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing ########################## ### Fast class methods ### ########################## -cpdef _solve_for_linear_terms(factory, eqns=None): +cpdef _solve_for_linear_terms(factory, list eqns=None): r""" Solve for a linear term occurring in a two-term equation, and for variables appearing in univariate single-term equations. @@ -66,6 +66,8 @@ cpdef _solve_for_linear_terms(factory, eqns=None): eqns = factory.ideal_basis cdef bint linear_terms_exist = False + cdef ETuple exp, rhs_exp + cdef int max_var cdef tuple eq_tup for eq_tup in eqns: # Only unflatten relevant polynomials @@ -83,7 +85,8 @@ cpdef _solve_for_linear_terms(factory, eqns=None): idx = has_appropriate_linear_term(eq_tup) if idx < 0: continue #The chosen term is guaranteed to be univariate in the largest variable - max_var = eq_tup[idx][0].nonzero_positions()[0] + exp = eq_tup[idx][0] + max_var = exp._data[0] if not factory._solved[max_var]: rhs_exp = eq_tup[(idx+1) % 2][0] rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx index a9664c79139..4270243eb26 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx @@ -8,7 +8,7 @@ Fast FusionRing methods for computing braid group representations # https://www.gnu.org/licenses/ # **************************************************************************** -import ctypes +from ctypes import cast, py_object cimport cython from sage.combinat.root_system.fast_parallel_fmats_methods cimport _fmat @@ -287,7 +287,7 @@ cpdef executor(tuple params): """ (fn_name, fr_id), args = params #Construct a reference to global FMatrix object in this worker's memory - fusion_ring_obj = ctypes.cast(fr_id, ctypes.py_object).value + fusion_ring_obj = cast(fr_id, py_object).value #Bind module method to FMatrix object in worker process, and call the method return mappers[fn_name](fusion_ring_obj,args) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index e1c8c553b2d..46e11f6aaf3 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -18,70 +18,70 @@ from multiprocessing import shared_memory import numpy as np cdef class KSHandler: - """ - Return a shared memory backed dict-like structure to manage the - ``_ks`` attribute of an F-matrix factory object. + def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): + """ + Return a shared memory backed dict-like structure to manage the + ``_ks`` attribute of an F-matrix factory object. - This structure implements a representation of the known squares dictionary - using a structured NumPy array backed by a contiguous shared memory - object. + This structure implements a representation of the known squares dictionary + using a structured NumPy array backed by a contiguous shared memory + object. - The structure mimics a dictionary of ``(idx, known_sq)`` pairs. Each - integer index corresponds to a variable and each ``known_sq`` is an - element of the F-matrix factory's base cyclotomic field. + The structure mimics a dictionary of ``(idx, known_sq)`` pairs. Each + integer index corresponds to a variable and each ``known_sq`` is an + element of the F-matrix factory's base cyclotomic field. - Each cyclotomic coefficient is stored as a list of numerators and a - list of denominators representing the rational coefficients. The - structured array also maintains ``known`` attribute that indicates - whether the structure contains an entry corresponding to the given index. + Each cyclotomic coefficient is stored as a list of numerators and a + list of denominators representing the rational coefficients. The + structured array also maintains ``known`` attribute that indicates + whether the structure contains an entry corresponding to the given index. - The parent process should construct this object without a - ``name`` attribute. Children processes use the ``name`` attribute, - accessed via ``self.shm.name`` to attach to the shared memory block. + The parent process should construct this object without a + ``name`` attribute. Children processes use the ``name`` attribute, + accessed via ``self.shm.name`` to attach to the shared memory block. - INPUT: + INPUT: - - ``n_slots`` -- The total number of F-symbols. - - ``field`` -- F-matrix factory's base cyclotomic field. - - ``use_mp`` -- a boolean indicating whether to construct a shared - memory block to back ``self``. - - ``name`` -- the name of a shared memory object - (used by child processes for attaching). - - ``init_data`` -- a dictionary or :class:`KSHandler` object containing - known squares for initialization, e.g. from a solver checkpoint. + - ``n_slots`` -- The total number of F-symbols. + - ``field`` -- F-matrix factory's base cyclotomic field. + - ``use_mp`` -- a boolean indicating whether to construct a shared + memory block to back ``self``. + - ``name`` -- the name of a shared memory object + (used by child processes for attaching). + - ``init_data`` -- a dictionary or :class:`KSHandler` object containing + known squares for initialization, e.g. from a solver checkpoint. - .. NOTE:: + .. NOTE:: - To properly dispose of shared memory resources, - ``self.shm.unlink()`` must be called before exiting. + To properly dispose of shared memory resources, + ``self.shm.unlink()`` must be called before exiting. - .. WARNING:: + .. WARNING:: - This structure does *not* cannot modify an entry that - has already been set. + This structure does *not* cannot modify an entry that + has already been set. - EXAMPLES:: + EXAMPLES:: - sage: from sage.combinat.root_system.shm_managers import KSHandler - sage: #Create shared data structure - sage: f = FMatrix(FusionRing("A1",2), inject_variables=True) - creating variables fx1..fx14 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - sage: n = f._poly_ring.ngens() - sage: ks = KSHandler(n,f._field,use_mp=True) - sage: #In the same shell or in a different shell, attach to fvars - sage: ks2 = KSHandler(n,f._field,name=ks.shm.name) - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] - sage: ks.update([poly_to_tup(p) for p in eqns]) - sage: for idx, sq in ks.items(): - ....: print("Index: {}, square: {}".format(idx, sq)) - ....: - Index: 1, square: 4 - Index: 3, square: -zeta32^4 + 1/19*zeta32^2 - sage: ks.shm.unlink() - """ - def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): + sage: from sage.combinat.root_system.shm_managers import KSHandler + sage: #Create shared data structure + sage: f = FMatrix(FusionRing("A1",2), inject_variables=True) + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + sage: n = f._poly_ring.ngens() + sage: ks = KSHandler(n,f._field,use_mp=True) + sage: #In the same shell or in a different shell, attach to fvars + sage: ks2 = KSHandler(n,f._field,name=ks.shm.name) + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] + sage: ks.update([poly_to_tup(p) for p in eqns]) + sage: for idx, sq in ks.items(): + ....: print("Index: {}, square: {}".format(idx, sq)) + ....: + Index: 1, square: 4 + Index: 3, square: -zeta32^4 + 1/19*zeta32^2 + sage: ks.shm.unlink() + """ cdef int n, d self.field = field n = n_slots @@ -192,6 +192,7 @@ cdef class KSHandler: @cython.nonecheck(False) @cython.wraparound(False) + @cython.infer_types(False) cdef setitem(self, int idx, rhs): """ Create an entry corresponding to the given index. @@ -552,13 +553,13 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("G2", 1), inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 - sage: p = f.get_worker_pool() + sage: f.start_worker_pool() sage: for sextuple, fvar in f._shared_fvars.items(): ....: if sextuple == (f1, f1, f1, f1, f1, f1): ....: f._tup_to_fpoly(fvar) ....: fx4 - sage: f.shutdown_worker_pool(p) + sage: f.shutdown_worker_pool() """ for sextuple in self.sext_to_idx: yield sextuple, self[sextuple] @@ -573,7 +574,7 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) - sage: for s, fvar in loads(dumps(fvars)).items(): + sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: sage: fvars.shm.unlink() From 66f9431cfc5a60c317a85615c400e93128120bc0 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 2 May 2021 21:31:26 -0400 Subject: [PATCH 070/414] set pool to None after shutdown --- src/sage/combinat/root_system/f_matrix.py | 1 + .../combinat/root_system/shm_managers.pyx | 110 +++++++++--------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index fabc75fc3ed..e4d56e2bac8 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1341,6 +1341,7 @@ def shutdown_worker_pool(self): self._ks.shm.unlink() self._shared_fvars.shm.unlink() del self.__dict__['_shared_fvars'] + self.pool = None def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): r""" diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 46e11f6aaf3..00522b84dcd 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -393,61 +393,6 @@ cdef class FvarsHandler: fvar = poly_to_tup(fvar) self[sextuple] = fvar - @cython.nonecheck(False) - @cython.wraparound(False) - def __setitem__(self, sextuple, fvar): - """ - Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, - create or overwrite an entry in the shared data structure - corresponding to the given sextuple. - - EXAMPLES:: - - sage: from sage.combinat.root_system.shm_managers import FvarsHandler - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) - creating variables fx1..fx27 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 - sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple) - sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) - sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) - sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) - sage: s, t, r = (f3, f2, f1, f2, f1, f3), (f3, f2, f3, f0, f1, f1), (f3, f3, f3, f1, f2, f2) - sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10 - True - sage: f._tup_to_fpoly(fvars[t]) == 0 - True - sage: f._tup_to_fpoly(fvars[r]) == -1/19 - True - sage: fvars.shm.unlink() - """ - cdef unsigned int cum, i, j, k, idx - cdef unsigned long denom - cdef long c - cdef ETuple exp - cdef NumberFieldElement_absolute coeff - idx = self.sext_to_idx[sextuple] - #Clear entry before inserting - self.fvars[idx] = np.zeros((1,), dtype=self.fvars_t) - cum = 0 - i = 0 - for exp, coeff in fvar: - #Handle constant coefficient - if exp._nonzero > 0: - self.fvars['ticks'][idx,i] = exp._nonzero - else: - self.fvars['ticks'][idx,i] = -1 - for j in range(2*exp._nonzero): - self.fvars['exp_data'][idx,cum] = exp._data[j] - cum += 1 - denom = coeff.denominator() - self.fvars['coeff_denom'][idx,i] = denom - for k, r in enumerate(coeff._coefficients()): - c = r * denom - self.fvars['coeff_nums'][idx,i,k] = c - i += 1 - self.fvars['modified'][idx] = True - @cython.nonecheck(False) @cython.wraparound(False) def __getitem__(self, sextuple): @@ -520,6 +465,61 @@ cdef class FvarsHandler: self.obj_cache[idx] = ret return ret + @cython.nonecheck(False) + @cython.wraparound(False) + def __setitem__(self, sextuple, fvar): + """ + Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, + create or overwrite an entry in the shared data structure + corresponding to the given sextuple. + + EXAMPLES:: + + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) + creating variables fx1..fx27 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple) + sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) + sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) + sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) + sage: s, t, r = (f3, f2, f1, f2, f1, f3), (f3, f2, f3, f0, f1, f1), (f3, f3, f3, f1, f2, f2) + sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10 + True + sage: f._tup_to_fpoly(fvars[t]) == 0 + True + sage: f._tup_to_fpoly(fvars[r]) == -1/19 + True + sage: fvars.shm.unlink() + """ + cdef unsigned int cum, i, j, k, idx + cdef unsigned long denom + cdef long c + cdef ETuple exp + cdef NumberFieldElement_absolute coeff + idx = self.sext_to_idx[sextuple] + #Clear entry before inserting + self.fvars[idx] = np.zeros((1,), dtype=self.fvars_t) + cum = 0 + i = 0 + for exp, coeff in fvar: + #Handle constant coefficient + if exp._nonzero > 0: + self.fvars['ticks'][idx,i] = exp._nonzero + else: + self.fvars['ticks'][idx,i] = -1 + for j in range(2*exp._nonzero): + self.fvars['exp_data'][idx,cum] = exp._data[j] + cum += 1 + denom = coeff.denominator() + self.fvars['coeff_denom'][idx,i] = denom + for k, r in enumerate(coeff._coefficients()): + c = r * denom + self.fvars['coeff_nums'][idx,i,k] = c + i += 1 + self.fvars['modified'][idx] = True + def __reduce__(self): """ Provide pickling / unpickling support for ``self.`` From 9c8c6c4c32292c2659f9cbe7dcf59cdea1cf2dd0 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 3 May 2021 00:51:55 -0400 Subject: [PATCH 071/414] FvarsHandler array attribute privatized --- src/sage/combinat/root_system/f_matrix.py | 40 +++++++++++-------- .../fast_parallel_fmats_methods.pyx | 14 +++++-- .../combinat/root_system/shm_managers.pxd | 4 +- .../combinat/root_system/shm_managers.pyx | 30 +++++++++----- 4 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index e4d56e2bac8..c8c0299e720 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1116,7 +1116,9 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params() sage: f._checkpoint(do_chkpt=True,status=2) @@ -1147,7 +1149,9 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params() sage: f.get_defining_equations('pentagons',output=False) @@ -1191,7 +1195,9 @@ def _restore_state(self,filename): sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params() sage: fvars = f._fvars @@ -1205,8 +1211,9 @@ def _restore_state(self,filename): sage: f = FMatrix(FusionRing("A1",2)) sage: f._reset_solver_state() sage: f._restore_state("fmatrix_solver_checkpoint_A12.pickle") - sage: fvars == f._fvars - True + sage: for sextuple, fvar in fvars.items(): + ....: assert fvar == f._fvars[sextuple] + ....: sage: ib == f.ideal_basis True sage: ks == f._ks @@ -1297,7 +1304,7 @@ class methods. n = self._poly_ring.ngens() self._ks = KSHandler(n,self._field,use_mp=True,init_data=self._ks) ks_names = self._ks.shm.name - self._shared_fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,init_data=self._fvars) + self._shared_fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,use_mp=True,init_data=self._fvars) fvar_names = self._shared_fvars.shm.name #Initialize worker pool processes args = (id(self), s_name, vd_name, ks_names, fvar_names) @@ -1336,12 +1343,12 @@ def shutdown_worker_pool(self): """ if self.pool is not None: self.pool.close() + self.pool = None self._solved.shm.unlink() self._var_degs.shm.unlink() self._ks.shm.unlink() self._shared_fvars.shm.unlink() del self.__dict__['_shared_fvars'] - self.pool = None def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): r""" @@ -1590,7 +1597,9 @@ def _triangular_elim(self,eqns=None,verbose=True): sage: gb = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis = sorted(gb, key=poly_tup_sortkey) - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: f._triangular_elim() Elimination epoch completed... 0 eqns remain in ideal basis sage: f.ideal_basis @@ -1600,10 +1609,8 @@ def _triangular_elim(self,eqns=None,verbose=True): if eqns is None: eqns = self.ideal_basis ret = False + using_mp = self.pool is not None while True: - #Reset modification cache - if self.pool is not None: - self._fvars.fvars['modified'][:] = False linear_terms_exist = _solve_for_linear_terms(self,eqns) if not linear_terms_exist: break @@ -1613,7 +1620,7 @@ def _triangular_elim(self,eqns=None,verbose=True): self._update_reduction_params(eqns=eqns) # n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) # eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] - if self.pool is not None and len(eqns) > self.mp_thresh: + if using_mp and len(eqns) > self.mp_thresh: n = self.pool._processes chunks = [[] for i in range(n)] for i, eq_tup in enumerate(eqns): @@ -1824,6 +1831,7 @@ def _get_component_variety(self,var,eqns): sage: f = FMatrix(FusionRing("G2",2)) sage: f.start_worker_pool() sage: f.get_defining_equations('hexagons',output=False) # long time + sage: f.shutdown_worker_pool() sage: partition = f._partition_eqns() # long time Partitioned 327 equations into 35 components of size: [27, 27, 27, 24, 24, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, @@ -1833,7 +1841,6 @@ def _get_component_variety(self,var,eqns): sage: eqns = partition[c] + [poly_to_tup(f._poly_ring.gen(216)-1)] # long time sage: f._get_component_variety(c,eqns) # long time [{216: -1, 292: -1, 319: 1}] - sage: f.shutdown_worker_pool() """ #Define smaller poly ring in component vars R = PolynomialRing(self._FR.field(), len(var), 'a', order='lex') @@ -2120,7 +2127,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Loading from a pickle with solved F-symbols if self._chkpt_status > 5: return - #max(cpu_count()-1,1) if use_mp: self.start_worker_pool() if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, self._poly_ring.ngens())) @@ -2132,10 +2138,12 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) - #Unzip _fvars and link to shared_memory structure if using multiprocessing - self._fvars = {sextuple: poly_to_tup(fvar) for sextuple, fvar in self._fvars.items()} + #Unzip _fvars and link to shared_memory structure if using multiprocessing if use_mp: self._fvars = self._shared_fvars + else: + n = self._poly_ring.ngens() + self._fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,init_data=self._fvars) self._checkpoint(checkpoint,1,verbose=verbose) if self._chkpt_status < 2: diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index c42b95616fe..21b3a939b41 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -49,7 +49,9 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _solve_for_linear_terms sage: _solve_for_linear_terms(f) True @@ -69,6 +71,8 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): cdef ETuple exp, rhs_exp cdef int max_var cdef tuple eq_tup + cdef FvarsHandler fvars = factory._fvars + fvars.clear_modified() for eq_tup in eqns: # Only unflatten relevant polynomials if len(eq_tup) > 2: @@ -78,7 +82,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): if len(eq_tup) == 1: vars = variables(eq_tup) if len(vars) == 1 and not factory._solved[vars[0]]: - factory._fvars[factory._idx_to_sextuple[vars[0]]] = tuple() + fvars[factory._idx_to_sextuple[vars[0]]] = tuple() factory._solved[vars[0]] = True linear_terms_exist = True if len(eq_tup) == 2: @@ -90,7 +94,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): if not factory._solved[max_var]: rhs_exp = eq_tup[(idx+1) % 2][0] rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] - factory._fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) + fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) factory._solved[max_var] = True linear_terms_exist = True return linear_terms_exist @@ -111,7 +115,9 @@ cpdef _backward_subs(factory): sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: f._fvars = {sextuple : poly_to_tup(rhs) for sextuple, rhs in f._fvars.items()} + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: n = f._poly_ring.ngens() + sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _solve_for_linear_terms sage: _solve_for_linear_terms(f) True diff --git a/src/sage/combinat/root_system/shm_managers.pxd b/src/sage/combinat/root_system/shm_managers.pxd index be13623646f..88f8f18aeef 100644 --- a/src/sage/combinat/root_system/shm_managers.pxd +++ b/src/sage/combinat/root_system/shm_managers.pxd @@ -19,5 +19,7 @@ cdef class FvarsHandler: cdef unsigned int ngens cdef fvars_t cdef NumberField field - cdef public np.ndarray fvars + cdef np.ndarray fvars cdef public shm + + cdef clear_modified(self) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 00522b84dcd..c8cefd53266 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -12,6 +12,7 @@ cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np from sage.combinat.root_system.poly_tup_engine cimport poly_to_tup, tup_fixes_sq +from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular from sage.rings.polynomial.polydict cimport ETuple from multiprocessing import shared_memory @@ -303,7 +304,7 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self,n_slots,field,idx_to_sextuple,init_data={},name=None,max_terms=20): + def __init__(self,n_slots,field,idx_to_sextuple,use_mp=False,name=None,init_data={},max_terms=20): """ Return a shared memory backed dict-like structure to manage the ``_fvars`` attribute of an F-matrix factory object. @@ -360,7 +361,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 - sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple) + sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=True) sage: #In the same shell or in a different shell, attach to fvars sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name) sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup @@ -382,17 +383,28 @@ cdef class FvarsHandler: self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} self.ngens = n_slots if name is None: - self.shm = shared_memory.SharedMemory(create=True,size=self.ngens*self.fvars_t.itemsize) - self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) + if use_mp: + self.shm = shared_memory.SharedMemory(create=True,size=self.ngens*self.fvars_t.itemsize) + self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) + else: + self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t) else: self.shm = shared_memory.SharedMemory(name=name) self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) #Populate with initialziation data for sextuple, fvar in init_data.items(): - if not isinstance(fvar, tuple): + if isinstance(fvar, MPolynomial_libsingular): fvar = poly_to_tup(fvar) + if isinstance(fvar, NumberFieldElement_absolute): + fvar = ((ETuple({},self.ngens), fvar),) self[sextuple] = fvar + cdef clear_modified(self): + """ + Reset tagged entries modified by parent process. + """ + self.fvars['modified'][:] = False + @cython.nonecheck(False) @cython.wraparound(False) def __getitem__(self, sextuple): @@ -411,7 +423,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("B7", 1), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple) + sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=True) sage: fvars[(f1, f2, f1, f2, f2, f2)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10) sage: fvars[f2, f2, f2, f2, f0, f0] = poly_to_tup(f._poly_ring.zero()) sage: fvars[f2, f1, f2, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) @@ -480,7 +492,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 - sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple) + sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=True) sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) @@ -529,7 +541,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("F4",1)) sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=True) sage: for s, fvar in loads(dumps(fvars)).items(): ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: @@ -573,7 +585,7 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: f = FMatrix(FusionRing("G2",1)) sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=True) sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: From f7b2a9a893bf5496ccfe2236a54e3a4e0db8c3d9 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 3 May 2021 02:04:52 -0400 Subject: [PATCH 072/414] documentation touch up --- src/sage/combinat/root_system/f_matrix.py | 14 +++++++------- .../root_system/fast_parallel_fmats_methods.pyx | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index c8c0299e720..0aa2a1bdfa9 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -35,7 +35,7 @@ poly_to_tup, _tup_to_poly, tup_to_univ_poly, _unflatten_coeffs, poly_tup_sortkey, - resize, + resize ) from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler from sage.graphs.graph import Graph @@ -1479,6 +1479,12 @@ def get_defining_equations(self,option,output=True): F-matrix solver. When computing the hexagon equations with the ``output=False`` option, the initial state of the F-symbols is used. + .. NOTE:: + + To set up the defining equations using parallel processing, + use :meth:`start_worker_pool` to initialize multiple processes + *before* calling this method. + EXAMPLES:: sage: f = FMatrix(FusionRing("B2",1)) @@ -1497,12 +1503,6 @@ def get_defining_equations(self,option,output=True): sage: pe = f.get_defining_equations('pentagons') sage: len(pe) 33 - - .. NOTE:: - - To set up the defining equations using parallel processing, - use :meth:`start_worker_pool` to initialize multiple processes - *before* calling this method. """ if not hasattr(self, '_nnz'): self._reset_solver_state() diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 21b3a939b41..cc245d717eb 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -321,10 +321,10 @@ cdef get_reduced_pentagons(factory, tuple mp_params): id_anyon = factory._FR.one() _field = factory._field cdef NumberFieldElement_absolute one = _field.one() + cdef MPolynomial_libsingular zero = factory._poly_ring.zero() + cdef KSHandler _ks = factory._ks factory._nnz = factory._get_known_nonz() cdef ETuple _nnz = factory._nnz - _ks = factory._ks - cdef MPolynomial_libsingular zero = factory._poly_ring.zero() #Computation loop it = product(basis,repeat=9) From 28bba61f9bc5aa51fdeec9afa83b5fd5cfd41126 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 3 May 2021 11:25:06 -0400 Subject: [PATCH 073/414] typed KSHandler in consumer methods --- src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx | 2 +- src/sage/combinat/root_system/poly_tup_engine.pxd | 2 +- src/sage/combinat/root_system/poly_tup_engine.pyx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index cc245d717eb..7b74852b233 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -352,7 +352,7 @@ cdef list update_reduce(factory, list eqns): #Pre-compute common parameters for speed _field = factory._field one = _field.one() - _ks = factory._ks + cdef KSHandler _ks = factory._ks #Update reduction params factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 7c889bce661..bf3a651363a 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -16,7 +16,7 @@ cpdef dict compute_known_powers(max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) cpdef tup_to_univ_poly(tuple eq_tup, univ_poly_ring) cpdef tuple poly_tup_sortkey(tuple eq_tup) -cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, known_sq, NumberFieldElement_absolute one) +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, KSHandler known_sq, NumberFieldElement_absolute one) cdef tuple _flatten_coeffs(tuple eq_tup) cpdef tuple _unflatten_coeffs(field, tuple eq_tup) cdef int has_appropriate_linear_term(tuple eq_tup) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 8db9e9f017e..bdd446ef675 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -426,7 +426,7 @@ cdef tuple to_monic(dict eq_dict, one): ret.append((ord_monoms[n-2-i], inv_lc * eq_dict[ord_monoms[n-2-i]])) return tuple(ret) -cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, known_sq, NumberFieldElement_absolute one): +cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, KSHandler known_sq, NumberFieldElement_absolute one): """ Return a tuple describing a monic polynomial with no known nonzero gcf and no known squares. From 9e4c552b215b74dab990c48f0743bc54899703d8 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 3 May 2021 17:58:57 -0400 Subject: [PATCH 074/414] usage of shm_handlers limited to cyclotomic field --- src/sage/combinat/root_system/f_matrix.py | 2 +- .../combinat/root_system/poly_tup_engine.pxd | 2 +- .../combinat/root_system/poly_tup_engine.pyx | 8 ++--- .../combinat/root_system/shm_managers.pyx | 34 +++++++++++++++---- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 0aa2a1bdfa9..5e275b46bd9 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -2018,7 +2018,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): for fx, rhs in numeric_fvars.items(): self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({},nvars),rhs),) _backward_subs(self) - self._fvars = {sextuple : constant_coeff(rhs) for sextuple, rhs in self._fvars.items()} + self._fvars = {sextuple : constant_coeff(rhs,self._field) for sextuple, rhs in self._fvars.items()} #Update base field attributes self._FR._field = self.field() diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index bf3a651363a..9be93601345 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -8,7 +8,7 @@ cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsing cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) cpdef list get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) -cpdef constant_coeff(tuple eq_tup) +cpdef constant_coeff(tuple eq_tup, field) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) cdef bint tup_fixes_sq(tuple eq_tup) cdef dict subs_squares(dict eq_dict, KSHandler known_sq) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index bdd446ef675..66d25b48551 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -277,7 +277,7 @@ cpdef list variables(tuple eq_tup): """ return degrees(eq_tup).nonzero_positions() -cpdef constant_coeff(tuple eq_tup): +cpdef constant_coeff(tuple eq_tup, field): r""" Return the constant coefficient of the polynomial represented by given tuple. @@ -287,18 +287,18 @@ cpdef constant_coeff(tuple eq_tup): sage: from sage.combinat.root_system.poly_tup_engine import constant_coeff sage: from sage.rings.polynomial.polydict import ETuple sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) - sage: constant_coeff(poly_tup) + sage: constant_coeff(poly_tup,QQ) -2/3 sage: R. = PolynomialRing(QQ) sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: constant_coeff(poly_to_tup(x**5 + x*y*z - 9)) + sage: constant_coeff(poly_to_tup(x**5 + x*y*z - 9),QQ) -9 """ cdef ETuple exp for exp, coeff in eq_tup: if exp.is_constant(): return coeff - return 0 + return field.zero() cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): """ diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index c8cefd53266..af7e157c004 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -1,5 +1,11 @@ """ -Shared memory managers for F-symbol attributes +Shared memory managers for F-symbol attributes. + +This module provides an implementation for shared dictionary like +state attributes required by the orthogonal F-matrix solver. + +Currently, the attributes only work when the base field of the FMatrix +factory is a cyclotomic field. """ # **************************************************************************** # Copyright (C) 2021 Guillermo Aboumrad @@ -12,6 +18,8 @@ cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np from sage.combinat.root_system.poly_tup_engine cimport poly_to_tup, tup_fixes_sq +from sage.rings.integer cimport Integer +from sage.rings.rational cimport Rational from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular from sage.rings.polynomial.polydict cimport ETuple @@ -121,11 +129,16 @@ cdef class KSHandler: if self.obj_cache[idx] is not None: return self.obj_cache[idx] cdef unsigned int i, d + cdef Integer num, denom + cdef Rational quo cdef list rat = list() cdef NumberFieldElement_absolute cyc_coeff d = self.field.degree() for i in range(d): - rat.append(self.ks_dat['nums'][idx,i] / self.ks_dat['denoms'][idx,i]) + num = Integer(self.ks_dat['nums'][idx,i]) + denom = Integer(self.ks_dat['denoms'][idx,i]) + quo = num / denom + rat.append(quo) cyc_coeff = self.field(rat) self.obj_cache[idx] = cyc_coeff return cyc_coeff @@ -452,10 +465,12 @@ cdef class FvarsHandler: else: return self.obj_cache[idx] cdef ETuple e = ETuple({}, self.ngens) - cdef unsigned int cum, i, j, nnz - cdef unsigned long d - cdef list poly_tup = list() + cdef unsigned int cum, i, j, k, nnz + cdef Integer d, num + cdef list poly_tup, rats + cdef Rational quo cdef tuple ret + poly_tup = list() cum = 0 for i in range(np.count_nonzero(self.fvars['ticks'][idx])): #Construct new ETuple for each monomial @@ -469,8 +484,13 @@ cdef class FvarsHandler: cum += 1 #Construct cyclotomic field coefficient - d = self.fvars['coeff_denom'][idx,i] - cyc_coeff = self.field([num / d for num in self.fvars['coeff_nums'][idx,i]]) + d = Integer(self.fvars['coeff_denom'][idx,i]) + rats = list() + for k in range(self.field.degree()): + num = Integer(self.fvars['coeff_nums'][idx,i,k]) + quo = num / d + rats.append(quo) + cyc_coeff = self.field(rats) poly_tup.append((exp, cyc_coeff)) ret = tuple(poly_tup) From 0854a532648e96d5b00c283e951cb14b5ccb0d63 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 4 May 2021 17:20:25 -0400 Subject: [PATCH 075/414] turned on mp for update_reduce, turned off buggy FvarsHandler caching mechanism --- src/sage/combinat/root_system/f_matrix.py | 2 +- src/sage/combinat/root_system/shm_managers.pyx | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 5e275b46bd9..f92b815d840 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1628,7 +1628,7 @@ def _triangular_elim(self,eqns=None,verbose=True): eqns = chunks else: eqns = [eqns] - eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=self.pool) + eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=self.pool,mp_thresh=0) eqns.sort(key=poly_tup_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index af7e157c004..271bf655ffb 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -459,11 +459,11 @@ cdef class FvarsHandler: if not sextuple in self.sext_to_idx: raise KeyError('Invalid sextuple {}'.format(sextuple)) cdef int idx = self.sext_to_idx[sextuple] - if idx in self.obj_cache: - if self.fvars['modified'][idx]: - del self.obj_cache[idx] - else: - return self.obj_cache[idx] + # if idx in self.obj_cache: + # if self.fvars['modified'][idx]: + # del self.obj_cache[idx] + # else: + # return self.obj_cache[idx] cdef ETuple e = ETuple({}, self.ngens) cdef unsigned int cum, i, j, k, nnz cdef Integer d, num @@ -494,7 +494,7 @@ cdef class FvarsHandler: poly_tup.append((exp, cyc_coeff)) ret = tuple(poly_tup) - self.obj_cache[idx] = ret + # self.obj_cache[idx] = ret return ret @cython.nonecheck(False) From 42d3e9e2bbc332dde69d97a2ce681ee02c8726e6 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 5 May 2021 12:53:01 -0400 Subject: [PATCH 076/414] using typed memoryviews in shm_managers to reduce python overhead and ensure correct typing --- .../combinat/root_system/shm_managers.pxd | 10 +- .../combinat/root_system/shm_managers.pyx | 111 +++++++++++------- 2 files changed, 77 insertions(+), 44 deletions(-) diff --git a/src/sage/combinat/root_system/shm_managers.pxd b/src/sage/combinat/root_system/shm_managers.pxd index 88f8f18aeef..77c74927915 100644 --- a/src/sage/combinat/root_system/shm_managers.pxd +++ b/src/sage/combinat/root_system/shm_managers.pxd @@ -7,7 +7,7 @@ cdef class KSHandler: cdef list obj_cache cdef np.ndarray ks_dat cdef NumberField field - cdef public shm + cdef public object shm cdef bint contains(self, int idx) cdef NumberFieldElement_absolute get(self, int idx) @@ -16,10 +16,12 @@ cdef class KSHandler: cdef class FvarsHandler: cdef dict sext_to_idx, obj_cache + cdef list modified_cache cdef unsigned int ngens - cdef fvars_t - cdef NumberField field + cdef object fvars_t cdef np.ndarray fvars - cdef public shm + cdef NumberField field + cdef public object shm cdef clear_modified(self) + # cdef update_cache(self) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 271bf655ffb..a802c4ee226 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -119,6 +119,7 @@ cdef class KSHandler: @cython.nonecheck(False) @cython.wraparound(False) + @cython.boundscheck(False) cdef NumberFieldElement_absolute get(self, int idx): """ Retrieve the known square corresponding to the given index, @@ -128,16 +129,23 @@ cdef class KSHandler: raise KeyError('Index {} does not correspond to a known square'.format(idx)) if self.obj_cache[idx] is not None: return self.obj_cache[idx] - cdef unsigned int i, d - cdef Integer num, denom - cdef Rational quo - cdef list rat = list() + cdef int d + cdef list rat + cdef Py_ssize_t i + cdef np.ndarray[np.int64_t,ndim=1] nums = self.ks_dat['nums'][idx] + # cdef np.int64_t[::1] num_view = nums + cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.ks_dat['denoms'][idx] + # cdef np.uint64_t[::1] denom_view = denoms + cdef np.int64_t num + cdef np.uint64_t denom cdef NumberFieldElement_absolute cyc_coeff + cdef Rational quo d = self.field.degree() + rat = list() for i in range(d): - num = Integer(self.ks_dat['nums'][idx,i]) - denom = Integer(self.ks_dat['denoms'][idx,i]) - quo = num / denom + num = nums[i] + denom = denoms[i] + quo = Integer(num) / Integer(denom) rat.append(quo) cyc_coeff = self.field(rat) self.obj_cache[idx] = cyc_coeff @@ -191,9 +199,9 @@ cdef class KSHandler: This method assumes every polynomial in ``eqns`` is *monic*. """ - cdef unsigned int i, idx cdef ETuple lm cdef list rhs + cdef Py_ssize_t i, idx cdef tuple eq_tup for i in range(len(eqns)): eq_tup = eqns[i] @@ -202,7 +210,10 @@ cdef class KSHandler: #eq_tup is guaranteed univariate, so we extract variable idx from lm lm = eq_tup[0][0] idx = lm._data[0] - self.setitem(idx, rhs) + try: + self.setitem(idx, rhs) + except OverflowError: + print("KS overflowed on index {} with value {}".format(idx,self.field(rhs))) @cython.nonecheck(False) @cython.wraparound(False) @@ -214,17 +225,21 @@ cdef class KSHandler: The ``rhs`` parameter may be a cyclotomic coefficient or its list/tuple representation. """ - cdef unsigned int i - cdef long long num - cdef unsigned long long denom + cdef Py_ssize_t i + cdef np.ndarray[np.int64_t,ndim=1] nums = self.ks_dat['nums'][idx] + cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.ks_dat['denoms'][idx] + cdef np.int64_t num + cdef np.uint64_t denom + cdef Rational quo self.ks_dat['known'][idx] = True if not isinstance(rhs, list): rhs = rhs._coefficients() for i in range(len(rhs)): - num = (rhs[i].numerator()) - denom = (rhs[i].denominator()) - self.ks_dat['nums'][idx,i] = num - self.ks_dat['denoms'][idx,i] = denom + quo = rhs[i] + num = quo.numerator() + denom = quo.denominator() + nums[i] = num + denoms[i] = denom cdef bint contains(self, int idx): """ @@ -362,8 +377,8 @@ cdef class FvarsHandler: .. WARNING:: - The current data structure supports up to 2**16-1 entries, - each with each monomial in each entry having at most 254 + The current data structure supports up to 2**16 entries, + with each monomial in each entry having at most 254 nonzero terms. On average, each of the ``max_terms`` monomials can have at most 50 terms. @@ -389,9 +404,9 @@ cdef class FvarsHandler: self.fvars_t = np.dtype([ ('modified','bool',(1,)), ('ticks', 'u1', (max_terms,)), - ('exp_data', 'u2', (max_terms*50,)), - ('coeff_nums','i4',(max_terms,d)), - ('coeff_denom','u4',(max_terms,)) + ('exp_data', 'u2', (max_terms*30,)), + ('coeff_nums',np.int64,(max_terms,d)), + ('coeff_denom',np.uint64,(max_terms,)) ]) self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} self.ngens = n_slots @@ -458,40 +473,48 @@ cdef class FvarsHandler: """ if not sextuple in self.sext_to_idx: raise KeyError('Invalid sextuple {}'.format(sextuple)) - cdef int idx = self.sext_to_idx[sextuple] + cdef Py_ssize_t idx = self.sext_to_idx[sextuple] # if idx in self.obj_cache: # if self.fvars['modified'][idx]: # del self.obj_cache[idx] # else: # return self.obj_cache[idx] - cdef ETuple e = ETuple({}, self.ngens) - cdef unsigned int cum, i, j, k, nnz + cdef ETuple e, exp + cdef int nnz cdef Integer d, num cdef list poly_tup, rats + cdef NumberFieldElement_absolute cyc_coeff + cdef Py_ssize_t cum, i, j, k cdef Rational quo cdef tuple ret + #Define memory views to reduce Python overhead and ensure correct typing + cdef np.ndarray[np.uint8_t,ndim=1] ticks = self.fvars['ticks'][idx] + cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] + cdef np.ndarray[np.int64_t,ndim=2] nums = self.fvars['coeff_nums'][idx] + cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.fvars['coeff_denom'][idx] + e = ETuple({}, self.ngens) poly_tup = list() cum = 0 - for i in range(np.count_nonzero(self.fvars['ticks'][idx])): + for i in range(np.count_nonzero(ticks)): #Construct new ETuple for each monomial exp = e._new() #Handle constant coeff - nnz = self.fvars['ticks'][idx,i] if self.fvars['ticks'][idx,i] < 255 else 0 + nnz = ticks[i] if ticks[i] < 255 else 0 exp._nonzero = nnz + # if nnz: exp._data = sig_malloc(sizeof(int)*nnz*2) for j in range(2*nnz): - exp._data[j] = self.fvars['exp_data'][idx,cum] + exp._data[j] = exp_data[cum] cum += 1 #Construct cyclotomic field coefficient - d = Integer(self.fvars['coeff_denom'][idx,i]) + d = Integer(denoms[i]) rats = list() for k in range(self.field.degree()): - num = Integer(self.fvars['coeff_nums'][idx,i,k]) + num = Integer(nums[i,k]) quo = num / d rats.append(quo) cyc_coeff = self.field(rats) - poly_tup.append((exp, cyc_coeff)) ret = tuple(poly_tup) # self.obj_cache[idx] = ret @@ -525,30 +548,38 @@ cdef class FvarsHandler: True sage: fvars.shm.unlink() """ - cdef unsigned int cum, i, j, k, idx - cdef unsigned long denom - cdef long c cdef ETuple exp + cdef np.int64_t c + cdef np.uint64_t denom cdef NumberFieldElement_absolute coeff + cdef Py_ssize_t cum, i, idx, j, k idx = self.sext_to_idx[sextuple] #Clear entry before inserting self.fvars[idx] = np.zeros((1,), dtype=self.fvars_t) + #Define memory views to reduce Python overhead and ensure correct typing + cdef np.ndarray[np.uint8_t,ndim=1] ticks = self.fvars['ticks'][idx] + cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] + cdef np.ndarray[np.int64_t,ndim=2] nums = self.fvars['coeff_nums'][idx] + cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.fvars['coeff_denom'][idx] cum = 0 i = 0 for exp, coeff in fvar: #Handle constant coefficient if exp._nonzero > 0: - self.fvars['ticks'][idx,i] = exp._nonzero + ticks[i] = exp._nonzero else: - self.fvars['ticks'][idx,i] = -1 + ticks[i] = -1 for j in range(2*exp._nonzero): - self.fvars['exp_data'][idx,cum] = exp._data[j] + exp_data[cum] = exp._data[j] cum += 1 denom = coeff.denominator() - self.fvars['coeff_denom'][idx,i] = denom - for k, r in enumerate(coeff._coefficients()): - c = r * denom - self.fvars['coeff_nums'][idx,i,k] = c + denoms[i] = denom + k = 0 + for r in coeff._coefficients(): + assert Integer(r * denom).nbits() < 63, "OverflowError in FvarsHandler.setitem: c {}, r {}, denom {}".format(c, r, denom) + c = (r * denom) + nums[i,k] = c + k += 1 i += 1 self.fvars['modified'][idx] = True From 3f781285a983f1e869c1a50b09d08f76870d0f63 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 5 May 2021 20:34:32 -0400 Subject: [PATCH 077/414] adding tests for shared memory data structures --- src/sage/combinat/root_system/f_matrix.py | 22 ++++++++++++++++--- .../fast_parallel_fmats_methods.pyx | 10 +++++++++ .../combinat/root_system/poly_tup_engine.pxd | 2 +- .../combinat/root_system/poly_tup_engine.pyx | 2 +- .../combinat/root_system/shm_managers.pyx | 12 ++++++++-- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index f92b815d840..68889703d8b 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -35,7 +35,8 @@ poly_to_tup, _tup_to_poly, tup_to_univ_poly, _unflatten_coeffs, poly_tup_sortkey, - resize + resize, + tup_fixes_sq ) from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler from sage.graphs.graph import Graph @@ -275,8 +276,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._FR = fusion_ring if inject_variables and (self._FR._fusion_labels is None): self._FR.fusion_labels(fusion_label, inject_variables=True) - if not self._FR.is_multiplicity_free(): - raise ValueError("FMatrix is only available for multiplicity free FusionRings") + # if not self._FR.is_multiplicity_free(): + # raise ValueError("FMatrix is only available for multiplicity free FusionRings") #Set up F-symbols entry by entry n_vars = self.findcases() self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) @@ -301,6 +302,10 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.mp_thresh = 10000 self.pool = None + #TESTS + self.test_fvars = dict() + self.test_ks = dict() + ####################### ### Class utilities ### ####################### @@ -1569,6 +1574,16 @@ def _update_reduction_params(self,eqns=None): if eqns is None: eqns = self.ideal_basis self._ks.update(eqns) + + #TESTS: + for i in range(len(eqns)): + eq_tup = eqns[i] + if tup_fixes_sq(eq_tup): + rhs = [-v for v in eq_tup[-1][1]] + self.test_ks[variables(eq_tup)[0]] = rhs + for i, sq in self._ks.items(): + assert sq == self._field(self.test_ks[i]), "{}: OG sq {}, shared sq {}".format(i, sq, self.test_ks[i]) + degs = get_variables_degrees(eqns) if degs: for i, d in enumerate(degs): @@ -2139,6 +2154,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) #Unzip _fvars and link to shared_memory structure if using multiprocessing + self.test_fvars = {k: poly_to_tup(p) for k, p in self._fvars.items()} if use_mp: self._fvars = self._shared_fvars else: diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 7b74852b233..bc7451a153b 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -85,6 +85,11 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): fvars[factory._idx_to_sextuple[vars[0]]] = tuple() factory._solved[vars[0]] = True linear_terms_exist = True + + #TEST: + s = factory._idx_to_sextuple[vars[0]] + factory.test_fvars[s] = tuple() + assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s],factory.test_fvars[s]) if len(eq_tup) == 2: idx = has_appropriate_linear_term(eq_tup) if idx < 0: continue @@ -97,6 +102,11 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) factory._solved[max_var] = True linear_terms_exist = True + + #TEST: + s = factory._idx_to_sextuple[max_var] + factory.test_fvars[s] = ((rhs_exp,rhs_coeff),) + assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s],factory.test_fvars[s]) return linear_terms_exist cpdef _backward_subs(factory): diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 9be93601345..81c13b94bac 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -10,7 +10,7 @@ cpdef list get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup, field) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) -cdef bint tup_fixes_sq(tuple eq_tup) +cpdef bint tup_fixes_sq(tuple eq_tup) cdef dict subs_squares(dict eq_dict, KSHandler known_sq) cpdef dict compute_known_powers(max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 66d25b48551..102825c6ad1 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -319,7 +319,7 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): new_tup.append((exp, coeff_map(coeff))) return tuple(new_tup) -cdef inline bint tup_fixes_sq(tuple eq_tup): +cpdef inline bint tup_fixes_sq(tuple eq_tup): r""" Determine if given equation fixes the square of a variable. diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index a802c4ee226..1a7bd669347 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -238,6 +238,10 @@ cdef class KSHandler: quo = rhs[i] num = quo.numerator() denom = quo.denominator() + if num > 2**32: + print("Large num encountered in KS",num) + if denom > 2**32: + print("Large denom encountered in KS",denom) nums[i] = num denoms[i] = denom @@ -332,7 +336,7 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self,n_slots,field,idx_to_sextuple,use_mp=False,name=None,init_data={},max_terms=20): + def __init__(self,n_slots,field,idx_to_sextuple,use_mp=False,name=None,init_data={},max_terms=20,n_bytes=16): """ Return a shared memory backed dict-like structure to manage the ``_fvars`` attribute of an F-matrix factory object. @@ -576,8 +580,12 @@ cdef class FvarsHandler: denoms[i] = denom k = 0 for r in coeff._coefficients(): - assert Integer(r * denom).nbits() < 63, "OverflowError in FvarsHandler.setitem: c {}, r {}, denom {}".format(c, r, denom) + assert Integer(r * denom).nbits() <= 63, "OverflowError in FvarsHandler.setitem: c {}, r {}, denom {}".format(c, r, denom) c = (r * denom) + if c > 2**32: + print("Large num encountered",c) + if denom > 2**32: + print("Large denom encountered",denom) nums[i,k] = c k += 1 i += 1 From 48558843bdad2d0347604e2b719365f65d6d5a92 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 7 May 2021 11:05:45 -0400 Subject: [PATCH 078/414] large integer capability for FvarsHandler --- src/sage/combinat/root_system/f_matrix.py | 2 +- .../fast_parallel_fmats_methods.pxd | 2 +- .../fast_parallel_fmats_methods.pyx | 28 +++++-- .../combinat/root_system/shm_managers.pxd | 2 +- .../combinat/root_system/shm_managers.pyx | 73 +++++++++++-------- 5 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 68889703d8b..4fdd4b54892 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -2032,7 +2032,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): self._fvars = {sextuple : apply_coeff_map(rhs,phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({},nvars),rhs),) - _backward_subs(self) + _backward_subs(self,flatten=False) self._fvars = {sextuple : constant_coeff(rhs,self._field) for sextuple, rhs in self._fvars.items()} #Update base field attributes diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd index f4f12e9c0d5..e0908ab5884 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd @@ -1,4 +1,4 @@ cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) -cpdef _backward_subs(factory) +cpdef _backward_subs(factory, bint flatten=*) cpdef executor(tuple params) cpdef _solve_for_linear_terms(factory, list eqns=*) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index bc7451a153b..2b3a158db68 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -72,12 +72,14 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): cdef int max_var cdef tuple eq_tup cdef FvarsHandler fvars = factory._fvars + cdef NumberFieldElement_absolute coeff, other + cdef tuple rhs_coeff fvars.clear_modified() for eq_tup in eqns: # Only unflatten relevant polynomials - if len(eq_tup) > 2: - continue - eq_tup = _unflatten_coeffs(factory._field, eq_tup) + # if len(eq_tup) > 2: + # continue + # eq_tup = _unflatten_coeffs(factory._field, eq_tup) if len(eq_tup) == 1: vars = variables(eq_tup) @@ -98,7 +100,10 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): max_var = exp._data[0] if not factory._solved[max_var]: rhs_exp = eq_tup[(idx+1) % 2][0] - rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] + # rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] + coeff = factory._field(list(eq_tup[(idx+1) % 2][1])) + other = factory._field(list(eq_tup[idx][1])) + rhs_coeff = tuple((-coeff / other)._coefficients()) fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) factory._solved[max_var] = True linear_terms_exist = True @@ -106,10 +111,10 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): #TEST: s = factory._idx_to_sextuple[max_var] factory.test_fvars[s] = ((rhs_exp,rhs_coeff),) - assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s],factory.test_fvars[s]) + assert _unflatten_coeffs(factory._field,factory.test_fvars[s]) == fvars[s], "OG value {}, Shared: {}".format(factory.test_fvars[s],fvars[s]) return linear_terms_exist -cpdef _backward_subs(factory): +cpdef _backward_subs(factory, bint flatten=True): r""" Perform backward substitution on ``self.ideal_basis``, traversing variables in reverse lexicographical order. @@ -165,7 +170,11 @@ cpdef _backward_subs(factory): for var_idx in variables(rhs) if factory._solved[var_idx]} if d: kp = compute_known_powers(get_variables_degrees([rhs]), d, one) - factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), _ks).items()) + # factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), _ks).items()) + res = tuple(subs_squares(subs(rhs,kp,one), _ks).items()) + if flatten: + res = _flatten_coeffs(res) + factory._fvars[sextuple] = res cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): """ @@ -272,7 +281,10 @@ cdef get_reduced_hexagons(factory, tuple mp_params): _ks = factory._ks #Computation loop - for i, sextuple in enumerate(product(basis, repeat=6)): + # for i, sextuple in enumerate(product(basis, repeat=6)): + it = product(basis, repeat=6) + for i in range(len(basis)**6): + sextuple = next(it) if i % n_proc == child_id: he = req_cy(basis,r_matrix,fvars,_Nk_ij,id_anyon,sextuple) if he: diff --git a/src/sage/combinat/root_system/shm_managers.pxd b/src/sage/combinat/root_system/shm_managers.pxd index 77c74927915..2a6a7e88ab4 100644 --- a/src/sage/combinat/root_system/shm_managers.pxd +++ b/src/sage/combinat/root_system/shm_managers.pxd @@ -17,7 +17,7 @@ cdef class KSHandler: cdef class FvarsHandler: cdef dict sext_to_idx, obj_cache cdef list modified_cache - cdef unsigned int ngens + cdef unsigned int ngens, bytes cdef object fvars_t cdef np.ndarray fvars cdef NumberField field diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 1a7bd669347..8ff30d654e7 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -17,7 +17,7 @@ factory is a cyclotomic field. cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np -from sage.combinat.root_system.poly_tup_engine cimport poly_to_tup, tup_fixes_sq +from sage.combinat.root_system.poly_tup_engine cimport poly_to_tup, tup_fixes_sq, _flatten_coeffs from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular @@ -336,7 +336,7 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self,n_slots,field,idx_to_sextuple,use_mp=False,name=None,init_data={},max_terms=20,n_bytes=16): + def __init__(self,n_slots,field,idx_to_sextuple,use_mp=False,name=None,init_data={},max_terms=20,n_bytes=32): """ Return a shared memory backed dict-like structure to manage the ``_fvars`` attribute of an F-matrix factory object. @@ -403,14 +403,16 @@ cdef class FvarsHandler: sage: fvars.shm.unlink() """ self.field = field - cdef int d = self.field.degree() self.obj_cache = dict() + cdef int d = self.field.degree() + self.bytes = n_bytes + cdef int slots = self.bytes // 8 self.fvars_t = np.dtype([ ('modified','bool',(1,)), ('ticks', 'u1', (max_terms,)), ('exp_data', 'u2', (max_terms*30,)), - ('coeff_nums',np.int64,(max_terms,d)), - ('coeff_denom',np.uint64,(max_terms,)) + ('coeff_nums',np.int64,(max_terms,d,slots)), + ('coeff_denom',np.uint64,(max_terms,d,slots)) ]) self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} self.ngens = n_slots @@ -426,9 +428,9 @@ cdef class FvarsHandler: #Populate with initialziation data for sextuple, fvar in init_data.items(): if isinstance(fvar, MPolynomial_libsingular): - fvar = poly_to_tup(fvar) + fvar = _flatten_coeffs(poly_to_tup(fvar)) if isinstance(fvar, NumberFieldElement_absolute): - fvar = ((ETuple({},self.ngens), fvar),) + fvar = ((ETuple({},self.ngens), tuple(fvar._coefficients())),) self[sextuple] = fvar cdef clear_modified(self): @@ -494,8 +496,8 @@ cdef class FvarsHandler: #Define memory views to reduce Python overhead and ensure correct typing cdef np.ndarray[np.uint8_t,ndim=1] ticks = self.fvars['ticks'][idx] cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] - cdef np.ndarray[np.int64_t,ndim=2] nums = self.fvars['coeff_nums'][idx] - cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.fvars['coeff_denom'][idx] + cdef np.ndarray[np.int64_t,ndim=3] nums = self.fvars['coeff_nums'][idx] + cdef np.ndarray[np.uint64_t,ndim=3] denoms = self.fvars['coeff_denom'][idx] e = ETuple({}, self.ngens) poly_tup = list() cum = 0 @@ -512,11 +514,12 @@ cdef class FvarsHandler: cum += 1 #Construct cyclotomic field coefficient - d = Integer(denoms[i]) + # d = Integer(denoms[i]) rats = list() for k in range(self.field.degree()): - num = Integer(nums[i,k]) - quo = num / d + num = Integer(list(nums[i,k]),2**63) + denom = Integer(list(denoms[i,k]),2**64) + quo = num / denom rats.append(quo) cyc_coeff = self.field(rats) poly_tup.append((exp, cyc_coeff)) @@ -553,21 +556,24 @@ cdef class FvarsHandler: sage: fvars.shm.unlink() """ cdef ETuple exp - cdef np.int64_t c - cdef np.uint64_t denom - cdef NumberFieldElement_absolute coeff - cdef Py_ssize_t cum, i, idx, j, k + cdef Integer num, denom + cdef tuple coeff_tup + cdef Py_ssize_t cum, i, idx, j, k, t + cdef Rational r idx = self.sext_to_idx[sextuple] #Clear entry before inserting self.fvars[idx] = np.zeros((1,), dtype=self.fvars_t) #Define memory views to reduce Python overhead and ensure correct typing cdef np.ndarray[np.uint8_t,ndim=1] ticks = self.fvars['ticks'][idx] cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] - cdef np.ndarray[np.int64_t,ndim=2] nums = self.fvars['coeff_nums'][idx] - cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.fvars['coeff_denom'][idx] + cdef np.ndarray[np.int64_t,ndim=3] nums = self.fvars['coeff_nums'][idx] + cdef np.ndarray[np.uint64_t,ndim=3] denoms = self.fvars['coeff_denom'][idx] + cdef list digits + #Initialize denominators to 1 + denoms[:,:,0] = 1 cum = 0 i = 0 - for exp, coeff in fvar: + for exp, coeff_tup in fvar: #Handle constant coefficient if exp._nonzero > 0: ticks[i] = exp._nonzero @@ -576,17 +582,26 @@ cdef class FvarsHandler: for j in range(2*exp._nonzero): exp_data[cum] = exp._data[j] cum += 1 - denom = coeff.denominator() - denoms[i] = denom k = 0 - for r in coeff._coefficients(): - assert Integer(r * denom).nbits() <= 63, "OverflowError in FvarsHandler.setitem: c {}, r {}, denom {}".format(c, r, denom) - c = (r * denom) - if c > 2**32: - print("Large num encountered",c) - if denom > 2**32: - print("Large denom encountered",denom) - nums[i,k] = c + for r in coeff_tup: + num, denom = r.as_integer_ratio() + assert denom != 0, "zero denominator error" + if abs(num) > 2**63 or denom > 2**63: + print("Large integers encountered in FvarsHandler", num, denom) + if abs(num) < 2**63: + nums[i,k,0] = num + else: + digits = num.digits(2**63) + assert len(digits) <= self.bytes // 8, "Numerator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(num,num.nbits()//8+1) + for t in range(len(digits)): + nums[i,k,t] = digits[t] + if denom < 2**64: + denoms[i,k,0] = denom + else: + digits = denom.digits(2**64) + assert len(digits) <= self.bytes // 8, "Denominator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(denom,denom.nbits()//8+1) + for t in range(len(digits)): + denoms[i,k,t] = digits[t] k += 1 i += 1 self.fvars['modified'][idx] = True From edacea4a7317853ddc42bd7e9e50d8bd34b1c806 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 7 May 2021 15:38:23 -0400 Subject: [PATCH 079/414] updated doctests to conform with new FvarsHandler usage --- src/sage/combinat/root_system/f_matrix.py | 14 ++++++++-- .../combinat/root_system/shm_managers.pyx | 28 ++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 4fdd4b54892..5893840799a 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -276,8 +276,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._FR = fusion_ring if inject_variables and (self._FR._fusion_labels is None): self._FR.fusion_labels(fusion_label, inject_variables=True) - # if not self._FR.is_multiplicity_free(): - # raise ValueError("FMatrix is only available for multiplicity free FusionRings") + if not self._FR.is_multiplicity_free(): + raise ValueError("FMatrix is only available for multiplicity free FusionRings") #Set up F-symbols entry by entry n_vars = self.findcases() self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) @@ -1248,6 +1248,8 @@ def _restore_state(self,filename): self._chkpt_status = 7 return self._fvars, self._solved, self._ks, self.ideal_basis, self._chkpt_status = state + #TESTS: + self.test_ks = {i: sq for i, sq in self._ks.items()} self._update_reduction_params() ################# @@ -2154,7 +2156,13 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) #Unzip _fvars and link to shared_memory structure if using multiprocessing - self.test_fvars = {k: poly_to_tup(p) for k, p in self._fvars.items()} + #TESTS: + for k, v in self._fvars.items(): + if isinstance(v, tuple): + self.test_fvars[k] = v + else: + self.test_fvars[k] = poly_to_tup(v) + # self.test_fvars = {k: poly_to_tup(p) for k, p in self._fvars.items()} if use_mp: self._fvars = self._shared_fvars else: diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 8ff30d654e7..f1da0fd47fa 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -397,7 +397,8 @@ cdef class FvarsHandler: sage: #In the same shell or in a different shell, attach to fvars sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name) sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: fvars[f2, f1, f2, f2, f0, f0] = poly_to_tup(fx5**5) + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) + sage: fvars[f2, f1, f2, f2, f0, f0] = rhs sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) fx5^5 sage: fvars.shm.unlink() @@ -431,6 +432,13 @@ cdef class FvarsHandler: fvar = _flatten_coeffs(poly_to_tup(fvar)) if isinstance(fvar, NumberFieldElement_absolute): fvar = ((ETuple({},self.ngens), tuple(fvar._coefficients())),) + if isinstance(fvar, tuple): + transformed = list() + for exp, c in fvar: + if isinstance(c, NumberFieldElement_absolute): + transformed.append((exp,tuple(c._coefficients()))) + if transformed: + fvar = tuple(transformed) self[sextuple] = fvar cdef clear_modified(self): @@ -458,9 +466,12 @@ cdef class FvarsHandler: creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=True) - sage: fvars[(f1, f2, f1, f2, f2, f2)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10) - sage: fvars[f2, f2, f2, f2, f0, f0] = poly_to_tup(f._poly_ring.zero()) - sage: fvars[f2, f1, f2, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10)) + sage: fvars[(f1, f2, f1, f2, f2, f2)] = rhs + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) + sage: fvars[f2, f2, f2, f2, f0, f0] = rhs + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(-1/19*f._poly_ring.one())) + sage: fvars[f2, f1, f2, f1, f2, f2] = rhs sage: s, t, r = (f1, f2, f1, f2, f2, f2), (f2, f2, f2, f2, f0, f0), (f2, f1, f2, f1, f2, f2) sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10 True @@ -543,9 +554,12 @@ cdef class FvarsHandler: creating variables fx1..fx27 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=True) - sage: fvars[(f3, f2, f1, f2, f1, f3)] = poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10) - sage: fvars[f3, f2, f3, f0, f1, f1] = poly_to_tup(f._poly_ring.zero()) - sage: fvars[f3, f3, f3, f1, f2, f2] = poly_to_tup(-1/19*f._poly_ring.one()) + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10)) + sage: fvars[(f3, f2, f1, f2, f1, f3)] = rhs + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) + sage: fvars[f3, f2, f3, f0, f1, f1] = rhs + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(-1/19*f._poly_ring.one())) + sage: fvars[f3, f3, f3, f1, f2, f2] = rhs sage: s, t, r = (f3, f2, f1, f2, f1, f3), (f3, f2, f3, f0, f1, f1), (f3, f3, f3, f1, f2, f2) sage: f._tup_to_fpoly(fvars[s]) == 1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10 True From bda8e972b7ee1db872bf38e950d665de5743b173 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sat, 8 May 2021 13:39:05 -0400 Subject: [PATCH 080/414] removed list conversion in singletons and improved findcases --- src/sage/combinat/root_system/f_matrix.py | 25 +++++++++++-------- .../combinat/root_system/poly_tup_engine.pyx | 2 +- .../combinat/root_system/shm_managers.pyx | 11 ++++---- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 5893840799a..e929614be8c 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -608,16 +608,21 @@ def findcases(self,output=False): if output: idx_map = dict() ret = dict() - for (a,b,c,d) in list(product(self._FR.basis(), repeat=4)): + # for (a,b,c,d) in list(product(self._FR.basis(), repeat=4)): + id_anyon = self._FR.one() + for (a,b,c,d) in product(self._FR.basis(), repeat=4): + if a == id_anyon or b == id_anyon or c == id_anyon: + continue for x in self.f_from(a, b, c, d): for y in self.f_to(a, b, c, d): - fm = self.fmat(a, b, c, d, x, y, data=False) - if fm is not None and fm not in [0,1]: - if output: - v = self._poly_ring.gens()[i] - ret[(a,b,c,d,x,y)] = v - idx_map[v] = (a, b, c, d, x, y) - i += 1 + # fm = self.fmat(a, b, c, d, x, y, data=False) + # if fm is not None and fm not in [0,1]: + if output: + # v = self._poly_ring.gens()[i] + v = self._poly_ring.gen(i) + ret[(a,b,c,d,x,y)] = v + idx_map[v] = (a, b, c, d, x, y) + i += 1 if output: return idx_map, ret else: @@ -640,7 +645,8 @@ def singletons(self): (ff,ft) = (self.f_from(a,b,c,d), self.f_to(a,b,c,d)) if len(ff) == 1 and len(ft) == 1: v = self._fvars.get((a,b,c,d,ff[0],ft[0]), None) - if v in self._poly_ring.gens(): + # if v in self._poly_ring.gens(): + if v in self._var_to_idx: ret.append(v) return ret @@ -2162,7 +2168,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self.test_fvars[k] = v else: self.test_fvars[k] = poly_to_tup(v) - # self.test_fvars = {k: poly_to_tup(p) for k, p in self._fvars.items()} if use_mp: self._fvars = self._shared_fvars else: diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 102825c6ad1..6ff2f3f1b32 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -123,7 +123,7 @@ cdef inline int has_appropriate_linear_term(tuple eq_tup): Determine whether the given tuple of pairs (of length 2) contains an *appropriate* linear term. - In this context, a linear term is said to be *appropriate* if the + In this context, a linear term is said to be *appropriate* if it is in the largest variable in the given polynomial (w.r.t. the degrevlex ordering), the monomial in which the linear term appears is univariate, and the linear term is not a common factor in diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index f1da0fd47fa..ab7389f2350 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -518,14 +518,13 @@ cdef class FvarsHandler: #Handle constant coeff nnz = ticks[i] if ticks[i] < 255 else 0 exp._nonzero = nnz - # if nnz: - exp._data = sig_malloc(sizeof(int)*nnz*2) - for j in range(2*nnz): - exp._data[j] = exp_data[cum] - cum += 1 + if nnz: + exp._data = sig_malloc(sizeof(int)*nnz*2) + for j in range(2*nnz): + exp._data[j] = exp_data[cum] + cum += 1 #Construct cyclotomic field coefficient - # d = Integer(denoms[i]) rats = list() for k in range(self.field.degree()): num = Integer(list(nums[i,k]),2**63) From ee5aed59fa1049bdc7806ac0101c1a4dfbd9be85 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 17 May 2021 15:21:41 -0400 Subject: [PATCH 081/414] some housekeeping --- src/sage/combinat/root_system/f_matrix.py | 8 +++---- .../fast_parallel_fmats_methods.pyx | 23 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index e929614be8c..ba4d2f73cc2 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -353,7 +353,7 @@ def clear_vars(self): sage: f.get_fvars()[some_key] fx0 """ - self._fvars = {self._var_to_sextuple[key] : key for key in self._var_to_sextuple} + self._fvars = {t : fx for fx, t in self._var_to_sextuple.items()} self._solved = list(False for fx in self._fvars) def _reset_solver_state(self): @@ -430,7 +430,7 @@ def _update_poly_ring_base_field(self,field): new_poly_ring = self._poly_ring.change_ring(field) nvars = self._poly_ring.ngens() #Do some appropriate conversions - self._singles = [new_poly_ring.gen(self._var_to_idx[fx]) for fx in self._singles] + self._singles = [new_poly_ring(fx) for fx in self._singles] self._var_to_idx = {new_poly_ring.gen(i): i for i in range(nvars)} self._var_to_sextuple = {new_poly_ring.gen(i): self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars)} self._poly_ring = new_poly_ring @@ -641,12 +641,12 @@ def singletons(self): True """ ret = [] + gens = set(self._poly_ring.gens()) for (a, b, c, d) in product(self._FR.basis(), repeat=4): (ff,ft) = (self.f_from(a,b,c,d), self.f_to(a,b,c,d)) if len(ff) == 1 and len(ft) == 1: v = self._fvars.get((a,b,c,d,ff[0],ft[0]), None) - # if v in self._poly_ring.gens(): - if v in self._var_to_idx: + if v in gens: ret.append(v) return ret diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 2b3a158db68..f086e637c1f 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -161,20 +161,20 @@ cpdef _backward_subs(factory, bint flatten=True): """ one = factory._field.one() _ks = factory._ks - vars = factory._poly_ring.gens() - n = len(vars) - for var in reversed(vars): - sextuple = factory._var_to_sextuple[var] - rhs = factory._fvars[sextuple] - d = {var_idx: factory._fvars[factory._idx_to_sextuple[var_idx]] - for var_idx in variables(rhs) if factory._solved[var_idx]} + fvars = factory._fvars + idx_to_sextuple = factory._idx_to_sextuple + solved = factory._solved + for i in range(len(idx_to_sextuple)-1,-1,-1): + sextuple = idx_to_sextuple[i] + rhs = fvars[sextuple] + d = {var_idx: fvars[idx_to_sextuple[var_idx]] + for var_idx in variables(rhs) if solved[var_idx]} if d: kp = compute_known_powers(get_variables_degrees([rhs]), d, one) - # factory._fvars[sextuple] = tuple(subs_squares(subs(rhs,kp,one), _ks).items()) - res = tuple(subs_squares(subs(rhs,kp,one), _ks).items()) + res = tuple(subs_squares(subs(rhs,kp,one),_ks).items()) if flatten: res = _flatten_coeffs(res) - factory._fvars[sextuple] = res + fvars[sextuple] = res cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): """ @@ -258,11 +258,10 @@ cdef get_reduced_hexagons(factory, tuple mp_params): #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) - # cdef dict fvars = factory._fvars cdef dict fvars cdef bint must_zip_up if not output: - fvars = {v: k for k, v in factory._var_to_sextuple.items()} + fvars = {s: factory._poly_ring.gen(i) for i, s in factory._idx_to_sextuple.items()} else: #Handle both cyclotomic and orthogonal solution method for k, v in factory._fvars.items(): From 9d9a56e556f7c6baf20ffd804b978d506a9c215a Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 17 May 2021 16:33:07 -0400 Subject: [PATCH 082/414] local shared_memory imports, min required version warnings in documentation --- src/sage/combinat/root_system/f_matrix.py | 41 ++++++++++++----- src/sage/combinat/root_system/fusion_ring.py | 4 +- .../combinat/root_system/shm_managers.pyx | 45 +++++++++++-------- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index ba4d2f73cc2..3baa26639a9 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -20,7 +20,7 @@ from copy import deepcopy from ctypes import cast, py_object from itertools import product, zip_longest -from multiprocessing import cpu_count, Pool, set_start_method, shared_memory +from multiprocessing import cpu_count, Pool, set_start_method import numpy as np from os import remove @@ -1279,7 +1279,8 @@ def start_worker_pool(self,processes=None): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",1)) - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: he = f.get_defining_equations('hexagons') sage: sorted(he) [fx0 - 1, @@ -1302,7 +1303,17 @@ class methods. Failure to call :meth:`shutdown_worker_pool` may result in a memory leak, since shared memory resources outlive the process that created them. + + .. WARNING:: + + Python 3.8+ is required. This method will raise an ``ImportError`` + if your system does not meet the version requirement. """ + #Try to import module requiring Python 3.8+ locally + try: + from multiprocessing import shared_memory + except ImportError: + raise ImportError("Failed to import shared_memory module. Requires Python 3.8+") try: set_start_method('fork') except RuntimeError: @@ -1330,8 +1341,8 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): fmats_obj._solved = shared_memory.ShareableList(name=solved_name) fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) n = fmats_obj._poly_ring.ngens() - fmats_obj._fvars = FvarsHandler(n,fmats_obj._field,fmats_obj._idx_to_sextuple,name=fvar_names) - fmats_obj._ks = KSHandler(n,fmats_obj._field,name=ks_names) + fmats_obj._fvars = FvarsHandler(n,fmats_obj._field,fmats_obj._idx_to_sextuple,name=fvar_names,use_mp=True) + fmats_obj._ks = KSHandler(n,fmats_obj._field,name=ks_names,use_mp=True) self.pool = Pool(processes=n,initializer=init,initargs=args) @@ -1350,7 +1361,8 @@ def shutdown_worker_pool(self): EXAMPLES:: sage: f = FMatrix(FusionRing("A1",3)) - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: he = f.get_defining_equations('hexagons') sage: f.shutdown_worker_pool() """ @@ -1389,7 +1401,8 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t sage: f._reset_solver_state() sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1,False)])) 11 - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: mp_params = [(i,f.pool._processes,True) for i in range(f.pool._processes)] sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=f.pool,chunksize=1,mp_thresh=0)) 33 @@ -1550,7 +1563,8 @@ def _tup_to_fpoly(self,eq_tup): sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f = FMatrix(FusionRing("C3",1)) - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: he = f.get_defining_equations('hexagons') sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True @@ -1567,7 +1581,8 @@ def _update_reduction_params(self,eqns=None): sage: f = FMatrix(FusionRing("A1",3)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: f.get_defining_equations('hexagons',output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup @@ -1796,7 +1811,8 @@ def _par_graph_gb(self,eqns=None,term_order="degrevlex",verbose=True): sage: f = FMatrix(FusionRing("F4",1)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: f.get_defining_equations('hexagons',output=False) sage: gb = f._par_graph_gb() Partitioned 10 equations into 2 components of size: @@ -1852,7 +1868,8 @@ def _get_component_variety(self,var,eqns): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",2)) - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: f.get_defining_equations('hexagons',output=False) # long time sage: f.shutdown_worker_pool() sage: partition = f._partition_eqns() # long time @@ -2087,7 +2104,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start - ``use_mp`` -- (default: ``True``) a boolean indicating whether to use multiprocessing to speed up calculation. The default value ``True`` is highly recommended, since parallel processing yields - results much more quickly. + results much more quickly. Python 3.8+ is required. This method will + raise an error if it cannot import the necessary components + (``shared_memory`` sub-module of ``multiprocessing``). - ``verbose`` -- (default: ``True``) a boolean indicating whether the solver should print out intermediate progress reports. diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index db546190cb7..9555869a73d 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -1249,7 +1249,9 @@ def get_braid_generators(self, we don't run the solver again. - ``use_mp`` -- (default: ``True``) a boolean indicating whether to use multiprocessing to speed up the computation; this is - highly recommended + highly recommended. Python 3.8+ is required. This method will + raise an error if it cannot import the necessary components + (``shared_memory`` sub-module of ``multiprocessing``). - ``verbose`` -- (default: ``True``) boolean indicating whether to be verbose with the computation diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index ab7389f2350..f54e902e4e7 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -23,7 +23,6 @@ from sage.rings.rational cimport Rational from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular from sage.rings.polynomial.polydict cimport ETuple -from multiprocessing import shared_memory import numpy as np cdef class KSHandler: @@ -67,7 +66,7 @@ cdef class KSHandler: .. WARNING:: - This structure does *not* cannot modify an entry that + This structure *cannot* modify an entry that has already been set. EXAMPLES:: @@ -80,7 +79,7 @@ cdef class KSHandler: sage: n = f._poly_ring.ngens() sage: ks = KSHandler(n,f._field,use_mp=True) sage: #In the same shell or in a different shell, attach to fvars - sage: ks2 = KSHandler(n,f._field,name=ks.shm.name) + sage: ks2 = KSHandler(n,f._field,name=ks.shm.name,use_mp=True) sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] sage: ks.update([poly_to_tup(p) for p in eqns]) @@ -101,18 +100,23 @@ cdef class KSHandler: ('denoms','u8',(d,)) ]) self.obj_cache = [None]*n - if name is None: - if use_mp: + if use_mp: + #Try to import module requiring Python 3.8+ locally + try: + from multiprocessing import shared_memory + except ImportError: + raise ImportError("Failed to import shared_memory module. Requires Python 3.8+") + if name is None: self.shm = shared_memory.SharedMemory(create=True,size=n*ks_t.itemsize) - self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) else: - self.ks_dat = np.ndarray((n,),dtype=ks_t) + self.shm = shared_memory.SharedMemory(name=name) + self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) + else: + self.ks_dat = np.ndarray((n,),dtype=ks_t) + if name is None: self.ks_dat['known'] = np.zeros((n,1),dtype='bool') self.ks_dat['nums'] = np.zeros((n,d),dtype='i8') self.ks_dat['denoms'] = np.ones((n,d),dtype='u8') - else: - self.shm = shared_memory.SharedMemory(name=name) - self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) #Populate initializer data for idx, sq in init_data.items(): self.setitem(idx,sq) @@ -395,7 +399,7 @@ cdef class FvarsHandler: Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=True) sage: #In the same shell or in a different shell, attach to fvars - sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name) + sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name,use_mp=True) sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) sage: fvars[f2, f1, f2, f2, f0, f0] = rhs @@ -417,15 +421,19 @@ cdef class FvarsHandler: ]) self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} self.ngens = n_slots - if name is None: - if use_mp: + if use_mp: + #Try to import module requiring Python 3.8+ locally + try: + from multiprocessing import shared_memory + except ImportError: + raise ImportError("Failed to import shared_memory module. Requires Python 3.8+") + if name is None: self.shm = shared_memory.SharedMemory(create=True,size=self.ngens*self.fvars_t.itemsize) - self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) else: - self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t) - else: - self.shm = shared_memory.SharedMemory(name=name) + self.shm = shared_memory.SharedMemory(name=name) self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) + else: + self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t) #Populate with initialziation data for sextuple, fvar in init_data.items(): if isinstance(fvar, MPolynomial_libsingular): @@ -652,7 +660,8 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("G2", 1), inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 - sage: f.start_worker_pool() + sage: if sys.version_info.minor >= 8: + ....: f.start_worker_pool() # Python 3.8+ is required sage: for sextuple, fvar in f._shared_fvars.items(): ....: if sextuple == (f1, f1, f1, f1, f1, f1): ....: f._tup_to_fpoly(fvar) From b819ed95f45940363ec6f9f0b5d8c53f3c1e8072 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 18 May 2021 14:54:54 -0400 Subject: [PATCH 083/414] switch to single process if shared_memory is not available --- src/sage/combinat/root_system/f_matrix.py | 44 +++++++++++++------ .../fast_parallel_fmats_methods.pyx | 1 - .../combinat/root_system/shm_managers.pyx | 8 ++-- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 3baa26639a9..6f1441ff9eb 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1268,14 +1268,32 @@ def start_worker_pool(self,processes=None): which may be used e.g. to set up defining equations using :meth:`get_defining_equations`. - This method creates the attribute ``self.pool``, and the worker + This method sets ``self``'s ``pool`` attribute. The worker pool may be used time and again. Upon initialization, each process - in the pool is attached to the necessary shared memory resources. + in the pool attaches to the necessary shared memory resources. When you are done using the worker pool, use :meth:`shutdown_worker_pool` to close the pool and properly dispose of shared memory resources. + .. NOTE:: + + Python 3.8+ is required, since the ``multiprocessing.shared_memory`` + module must be imported. If we fail to import the ``shared_memory`` + module, ``self.pool`` is set to ``None``, no worker pool is created, + and all subsequent calculations will run serially. + + INPUT:: + + - ``processes`` -- an integer indicating the number of workers + in the pool. If left unspecified, the number of workers is + equals the number of processors available. + + OUTPUT:: + + Returns a boolean indicating whether a worker pool was successfully + initialized. + EXAMPLES:: sage: f = FMatrix(FusionRing("G2",1)) @@ -1303,17 +1321,13 @@ class methods. Failure to call :meth:`shutdown_worker_pool` may result in a memory leak, since shared memory resources outlive the process that created them. - - .. WARNING:: - - Python 3.8+ is required. This method will raise an ``ImportError`` - if your system does not meet the version requirement. """ #Try to import module requiring Python 3.8+ locally try: from multiprocessing import shared_memory except ImportError: - raise ImportError("Failed to import shared_memory module. Requires Python 3.8+") + self.pool = None + return False try: set_start_method('fork') except RuntimeError: @@ -1345,6 +1359,7 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): fmats_obj._ks = KSHandler(n,fmats_obj._field,name=ks_names,use_mp=True) self.pool = Pool(processes=n,initializer=init,initargs=args) + return True def shutdown_worker_pool(self): """ @@ -2104,9 +2119,10 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start - ``use_mp`` -- (default: ``True``) a boolean indicating whether to use multiprocessing to speed up calculation. The default value ``True`` is highly recommended, since parallel processing yields - results much more quickly. Python 3.8+ is required. This method will - raise an error if it cannot import the necessary components - (``shared_memory`` sub-module of ``multiprocessing``). + results much more quickly. Python 3.8+ is required, since the + ``multiprocessing.shared_memory`` module must be imported. If we + fail to import the ``shared_memory`` module, the solver runs + serially. - ``verbose`` -- (default: ``True``) a boolean indicating whether the solver should print out intermediate progress reports. @@ -2169,7 +2185,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Loading from a pickle with solved F-symbols if self._chkpt_status > 5: return - if use_mp: self.start_worker_pool() + loads_shared_memory = False + if use_mp: + loads shared_memory = self.start_worker_pool() if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, self._poly_ring.ngens())) @@ -2187,7 +2205,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self.test_fvars[k] = v else: self.test_fvars[k] = poly_to_tup(v) - if use_mp: + if use_mp and loads_shared_memory: self._fvars = self._shared_fvars else: n = self._poly_ring.ngens() diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index f086e637c1f..f2f09a038e4 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -25,7 +25,6 @@ from sage.rings.polynomial.polydict cimport ETuple from ctypes import cast, py_object from itertools import product -from multiprocessing import shared_memory from sage.rings.ideal import Ideal from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index f54e902e4e7..e2a646b9269 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -318,7 +318,7 @@ cdef class KSHandler: Index: 25, sq: 1 Index: 26, sq: 1 """ - cdef unsigned int i + cdef Py_ssize_t i for i in range(self.ks_dat.size): if self.ks_dat['known'][i]: yield i, self.get(i) @@ -505,7 +505,7 @@ cdef class FvarsHandler: # else: # return self.obj_cache[idx] cdef ETuple e, exp - cdef int nnz + cdef int count, nnz cdef Integer d, num cdef list poly_tup, rats cdef NumberFieldElement_absolute cyc_coeff @@ -520,7 +520,9 @@ cdef class FvarsHandler: e = ETuple({}, self.ngens) poly_tup = list() cum = 0 - for i in range(np.count_nonzero(ticks)): + # for i in range(np.count_nonzero(ticks)): + count = np.count_nonzero(ticks) + for i in range(count): #Construct new ETuple for each monomial exp = e._new() #Handle constant coeff From f7cbee769faa284566b792c8f47337d5c2939869 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 19 May 2021 12:45:11 -0400 Subject: [PATCH 084/414] syntax error loads shared_memory corrected --- src/sage/combinat/root_system/f_matrix.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 6f1441ff9eb..cc06159d657 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -1283,16 +1283,16 @@ def start_worker_pool(self,processes=None): module, ``self.pool`` is set to ``None``, no worker pool is created, and all subsequent calculations will run serially. - INPUT:: + INPUT: - ``processes`` -- an integer indicating the number of workers in the pool. If left unspecified, the number of workers is equals the number of processors available. - OUTPUT:: + OUTPUT: - Returns a boolean indicating whether a worker pool was successfully - initialized. + This method returns a boolean indicating whether a worker pool + was successfully initialized. EXAMPLES:: @@ -2187,7 +2187,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start return loads_shared_memory = False if use_mp: - loads shared_memory = self.start_worker_pool() + loads_shared_memory = self.start_worker_pool() if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, self._poly_ring.ngens())) From 0dbea214bcb9bf3e90adc438f0ecdadbc842d773 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 20 May 2021 16:42:08 -0400 Subject: [PATCH 085/414] corrected doctest usage for Python < 3.8 --- src/sage/combinat/root_system/f_matrix.py | 42 +++++------ .../combinat/root_system/shm_managers.pyx | 70 ++++++++++++++----- 2 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index cc06159d657..e31a823c96b 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -208,13 +208,12 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab :: - sage: f.get_defining_equations("pentagons")[1:3] - [fx9*fx12 - fx2*fx13, fx3*fx8 - fx4*fx9] - sage: f.get_defining_equations("hexagons")[1:3] - [fx11*fx12 + (-zeta128^32)*fx13^2 + (-zeta128^48)*fx13, - fx10*fx11 + (-zeta128^32)*fx11*fx13 + (-zeta128^16)*fx11] - sage: f.get_orthogonality_constraints()[1:3] - [fx1^2 - 1, fx2^2 - 1] + sage: sorted(f.get_defining_equations("pentagons"))[1:3] + [fx9*fx12 - fx2*fx13, fx4*fx11 - fx2*fx13] + sage: sorted(f.get_defining_equations("hexagons"))[1:3] + [fx6 - 1, fx2 + 1] + sage: sorted(f.get_orthogonality_constraints())[1:3] + [fx10*fx11 + fx12*fx13, fx10*fx11 + fx12*fx13] There are two methods available to compute an F-matrix. The first, :meth:`find_cyclotomic_solution` uses only @@ -1297,8 +1296,7 @@ def start_worker_pool(self,processes=None): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",1)) - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: he = f.get_defining_equations('hexagons') sage: sorted(he) [fx0 - 1, @@ -1376,8 +1374,7 @@ def shutdown_worker_pool(self): EXAMPLES:: sage: f = FMatrix(FusionRing("A1",3)) - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: he = f.get_defining_equations('hexagons') sage: f.shutdown_worker_pool() """ @@ -1416,9 +1413,11 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t sage: f._reset_solver_state() sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1,False)])) 11 - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required - sage: mp_params = [(i,f.pool._processes,True) for i in range(f.pool._processes)] + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: if is_shared_memory_available: + ....: mp_params = [(i,f.pool._processes,True) for i in range(f.pool._processes)] + ....: else: + ....: mp_params = [(0,1,True)] sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=f.pool,chunksize=1,mp_thresh=0)) 33 sage: f.shutdown_worker_pool() @@ -1578,8 +1577,7 @@ def _tup_to_fpoly(self,eq_tup): sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: f = FMatrix(FusionRing("C3",1)) - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: he = f.get_defining_equations('hexagons') sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True @@ -1596,14 +1594,14 @@ def _update_reduction_params(self,eqns=None): sage: f = FMatrix(FusionRing("A1",3)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: f.get_defining_equations('hexagons',output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 - sage: f._fvars = f._shared_fvars + sage: if is_shared_memory_available: + ....: f._fvars = f._shared_fvars sage: f._triangular_elim(verbose=False) # indirect doctest sage: f.ideal_basis [] @@ -1826,8 +1824,7 @@ def _par_graph_gb(self,eqns=None,term_order="degrevlex",verbose=True): sage: f = FMatrix(FusionRing("F4",1)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: f.get_defining_equations('hexagons',output=False) sage: gb = f._par_graph_gb() Partitioned 10 equations into 2 components of size: @@ -1883,8 +1880,7 @@ def _get_component_variety(self,var,eqns): EXAMPLES:: sage: f = FMatrix(FusionRing("G2",2)) - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: f.get_defining_equations('hexagons',output=False) # long time sage: f.shutdown_worker_pool() sage: partition = f._partition_eqns() # long time diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index e2a646b9269..0ccb3cabcaf 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -53,7 +53,10 @@ cdef class KSHandler: - ``n_slots`` -- The total number of F-symbols. - ``field`` -- F-matrix factory's base cyclotomic field. - ``use_mp`` -- a boolean indicating whether to construct a shared - memory block to back ``self``. + memory block to back ``self``. Requires Python 3.8+, since we + must import the ``multiprocessing.shared_memory`` module. Attempting + to initialize when ``multiprocessing.shared_memory`` is not available + results in an ``ImportError``. - ``name`` -- the name of a shared memory object (used by child processes for attaching). - ``init_data`` -- a dictionary or :class:`KSHandler` object containing @@ -77,9 +80,12 @@ cdef class KSHandler: creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() - sage: ks = KSHandler(n,f._field,use_mp=True) + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available) sage: #In the same shell or in a different shell, attach to fvars - sage: ks2 = KSHandler(n,f._field,name=ks.shm.name,use_mp=True) + sage: ks2 = KSHandler(n,f._field,name=ks.shm.name,use_mp=is_shared_memory_available) + sage: if not is_shared_memory_available: + ....: ks2 = ks sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] sage: ks.update([poly_to_tup(p) for p in eqns]) @@ -89,6 +95,7 @@ cdef class KSHandler: Index: 1, square: 4 Index: 3, square: -zeta32^4 + 1/19*zeta32^2 sage: ks.shm.unlink() + sage: f.shutdown_worker_pool() """ cdef int n, d self.field = field @@ -267,12 +274,16 @@ cdef class KSHandler: sage: f.get_orthogonality_constraints(output=False) sage: from sage.combinat.root_system.shm_managers import KSHandler sage: n = f._poly_ring.ngens() - sage: ks = KSHandler(n,f._field,use_mp=True,init_data=f._ks) + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available,init_data=f._ks) sage: #In the same shell or in a different one, attach to shared memory handler - sage: k2 = KSHandler(n,f._field,name=ks.shm.name) + sage: k2 = KSHandler(n,f._field,name=ks.shm.name,use_mp=is_shared_memory_available) + sage: if not is_shared_memory_available: + ....: k2 = ks sage: ks == k2 True sage: ks.shm.unlink() + sage: f.shutdown_worker_pool() """ ret = True for idx, sq in self.items(): @@ -377,18 +388,32 @@ cdef class FvarsHandler: - ``max_terms`` -- maximum number of terms in each entry. Since we use contiguous C-style memory blocks, the size of the block must be known in advance. + - ``use_mp`` -- a boolean indicating whether the array backing + ``self`` should be placed in shared memory. This requires + Python 3.8+, since we must import the + ``multiprocessing.shared_memory`` module. Attempting to initialize + when ``multiprocessing.shared_memory`` is not available results in + an ``ImportError``. + - ``n_bytes`` -- the number of bytes that should be allocated for + each numerator and each denominator stored by the structure. .. NOTE:: To properly dispose of shared memory resources, ``self.shm.unlink()`` must be called before exiting. + .. NOTE:: + + If you ever encounter an ``OverflowError`` when running the + :meth:`FMatrix.find_orthogonal_solution`` solver, consider + increasing the parameter ``n_bytes``. + .. WARNING:: The current data structure supports up to 2**16 entries, with each monomial in each entry having at most 254 nonzero terms. On average, each of the ``max_terms`` monomials - can have at most 50 terms. + can have at most 30 terms. EXAMPLES:: @@ -397,15 +422,19 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 - sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=True) + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=is_shared_memory_available) sage: #In the same shell or in a different shell, attach to fvars - sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name,use_mp=True) + sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name,use_mp=is_shared_memory_available) + sage: if not is_shared_memory_available: + ....: fvars2 = fvars sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) sage: fvars[f2, f1, f2, f2, f0, f0] = rhs sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) fx5^5 sage: fvars.shm.unlink() + sage: f.shutdown_worker_pool() """ self.field = field self.obj_cache = dict() @@ -473,7 +502,8 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("B7", 1), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=True) + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=is_shared_memory_available) sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f1, f2, f1, f2, f2, f2)] = rhs sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) @@ -488,6 +518,7 @@ cdef class FvarsHandler: sage: f._tup_to_fpoly(fvars[r]) == -1/19 True sage: fvars.shm.unlink() + sage: f.shutdown_worker_pool() .. NOTE:: @@ -562,7 +593,8 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 - sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=True) + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=is_shared_memory_available) sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f3, f2, f1, f2, f1, f3)] = rhs sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) @@ -577,6 +609,7 @@ cdef class FvarsHandler: sage: f._tup_to_fpoly(fvars[r]) == -1/19 True sage: fvars.shm.unlink() + sage: f.shutdown_worker_pool() """ cdef ETuple exp cdef Integer num, denom @@ -638,11 +671,13 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("F4",1)) sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=True) + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=is_shared_memory_available) sage: for s, fvar in loads(dumps(fvars)).items(): ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: sage: fvars.shm.unlink() + sage: f.shutdown_worker_pool() """ n = self.fvars.size idx_map = {i: s for s, i in self.sext_to_idx.items()} @@ -662,14 +697,13 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("G2", 1), inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 - sage: if sys.version_info.minor >= 8: - ....: f.start_worker_pool() # Python 3.8+ is required - sage: for sextuple, fvar in f._shared_fvars.items(): + sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: shared_fvars = FvarsHandler(5,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: for sextuple, fvar in shared_fvars.items(): ....: if sextuple == (f1, f1, f1, f1, f1, f1): ....: f._tup_to_fpoly(fvar) ....: fx4 - sage: f.shutdown_worker_pool() """ for sextuple in self.sext_to_idx: yield sextuple, self[sextuple] @@ -683,10 +717,12 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: f = FMatrix(FusionRing("G2",1)) sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=True) - sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=is_shared_memory_available) + sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: sage: fvars.shm.unlink() + sage: f.shutdown_worker_pool() """ return FvarsHandler(n,field,idx_map,init_data=init_data) From 2c628c5d6c5fd0212e0697203527756ca7fc960c Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 21 May 2021 10:35:21 -0400 Subject: [PATCH 086/414] new caching mechanism for shm handlers --- src/sage/combinat/root_system/f_matrix.py | 31 ++++--- .../fast_parallel_fmats_methods.pyx | 8 -- .../combinat/root_system/shm_managers.pxd | 9 +- .../combinat/root_system/shm_managers.pyx | 92 ++++++++++++++----- 4 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index e31a823c96b..e87156feebc 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -22,7 +22,7 @@ from itertools import product, zip_longest from multiprocessing import cpu_count, Pool, set_start_method import numpy as np -from os import remove +from os import getpid, remove from sage.combinat.root_system.fast_parallel_fmats_methods import ( _backward_subs, _solve_for_linear_terms, @@ -1333,6 +1333,9 @@ class methods. if not hasattr(self, '_nnz'): self._reset_solver_state() #Set up shared memory resource handlers + n_proc = cpu_count() if processes is None else processes + self._pid_list = shared_memory.ShareableList([0]*(n_proc+1)) + pids_name = self._pid_list.shm.name self._solved = shared_memory.ShareableList(self._solved) s_name = self._solved.shm.name self._var_degs = shared_memory.ShareableList(self._var_degs) @@ -1340,12 +1343,11 @@ class methods. n = self._poly_ring.ngens() self._ks = KSHandler(n,self._field,use_mp=True,init_data=self._ks) ks_names = self._ks.shm.name - self._shared_fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,use_mp=True,init_data=self._fvars) + self._shared_fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name,init_data=self._fvars) fvar_names = self._shared_fvars.shm.name #Initialize worker pool processes - args = (id(self), s_name, vd_name, ks_names, fvar_names) - n = cpu_count() if processes is None else processes - def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): + args = (id(self), s_name, vd_name, ks_names, fvar_names, n_proc, pids_name) + def init(fmats_id, solved_name, vd_name, ks_names, fvar_names, n_proc, pids_name): """ Connect worker process to shared memory resources """ @@ -1353,10 +1355,14 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names): fmats_obj._solved = shared_memory.ShareableList(name=solved_name) fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) n = fmats_obj._poly_ring.ngens() - fmats_obj._fvars = FvarsHandler(n,fmats_obj._field,fmats_obj._idx_to_sextuple,name=fvar_names,use_mp=True) - fmats_obj._ks = KSHandler(n,fmats_obj._field,name=ks_names,use_mp=True) - - self.pool = Pool(processes=n,initializer=init,initargs=args) + K = fmats_obj._field + fmats_obj._fvars = FvarsHandler(n,K,fmats_obj._idx_to_sextuple,name=fvar_names,use_mp=n_proc,pids_name=pids_name) + fmats_obj._ks = KSHandler(n,K,name=ks_names,use_mp=True) + + self.pool = Pool(processes=n_proc,initializer=init,initargs=args) + self._pid_list[0] = getpid() + for i, p in enumerate(self.pool._pool): + self._pid_list[i+1] = p.pid return True def shutdown_worker_pool(self): @@ -1385,6 +1391,7 @@ def shutdown_worker_pool(self): self._var_degs.shm.unlink() self._ks.shm.unlink() self._shared_fvars.shm.unlink() + self._pid_list.shm.unlink() del self.__dict__['_shared_fvars'] def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): @@ -1660,18 +1667,18 @@ def _triangular_elim(self,eqns=None,verbose=True): if eqns is None: eqns = self.ideal_basis ret = False - using_mp = self.pool is not None + # using_mp = self.pool is not None while True: linear_terms_exist = _solve_for_linear_terms(self,eqns) if not linear_terms_exist: break _backward_subs(self) - #Compute new reduction params, send to child processes if any, and update eqns + #Compute new reduction params and update eqns self._update_reduction_params(eqns=eqns) # n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) # eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] - if using_mp and len(eqns) > self.mp_thresh: + if self.pool is not None and len(eqns) > self.mp_thresh: n = self.pool._processes chunks = [[] for i in range(n)] for i, eq_tup in enumerate(eqns): diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index f2f09a038e4..029b321ac58 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -73,13 +73,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): cdef FvarsHandler fvars = factory._fvars cdef NumberFieldElement_absolute coeff, other cdef tuple rhs_coeff - fvars.clear_modified() for eq_tup in eqns: - # Only unflatten relevant polynomials - # if len(eq_tup) > 2: - # continue - # eq_tup = _unflatten_coeffs(factory._field, eq_tup) - if len(eq_tup) == 1: vars = variables(eq_tup) if len(vars) == 1 and not factory._solved[vars[0]]: @@ -99,7 +93,6 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): max_var = exp._data[0] if not factory._solved[max_var]: rhs_exp = eq_tup[(idx+1) % 2][0] - # rhs_coeff = -eq_tup[(idx+1) % 2][1] / eq_tup[idx][1] coeff = factory._field(list(eq_tup[(idx+1) % 2][1])) other = factory._field(list(eq_tup[idx][1])) rhs_coeff = tuple((-coeff / other)._coefficients()) @@ -279,7 +272,6 @@ cdef get_reduced_hexagons(factory, tuple mp_params): _ks = factory._ks #Computation loop - # for i, sextuple in enumerate(product(basis, repeat=6)): it = product(basis, repeat=6) for i in range(len(basis)**6): sextuple = next(it) diff --git a/src/sage/combinat/root_system/shm_managers.pxd b/src/sage/combinat/root_system/shm_managers.pxd index 2a6a7e88ab4..32dd0073091 100644 --- a/src/sage/combinat/root_system/shm_managers.pxd +++ b/src/sage/combinat/root_system/shm_managers.pxd @@ -16,12 +16,9 @@ cdef class KSHandler: cdef class FvarsHandler: cdef dict sext_to_idx, obj_cache - cdef list modified_cache - cdef unsigned int ngens, bytes - cdef object fvars_t + cdef int bytes, ngens cdef np.ndarray fvars cdef NumberField field + cdef object fvars_t, pid_list + cdef Py_ssize_t child_id cdef public object shm - - cdef clear_modified(self) - # cdef update_cache(self) diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 0ccb3cabcaf..674ba9fc205 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -24,6 +24,7 @@ from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libs from sage.rings.polynomial.polydict cimport ETuple import numpy as np +from os import getpid cdef class KSHandler: def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): @@ -351,7 +352,7 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self,n_slots,field,idx_to_sextuple,use_mp=False,name=None,init_data={},max_terms=20,n_bytes=32): + def __init__(self,n_slots,field,idx_to_sextuple,use_mp=0,pids_name=None,name=None,init_data={},max_terms=20,n_bytes=32): """ Return a shared memory backed dict-like structure to manage the ``_fvars`` attribute of an F-matrix factory object. @@ -388,12 +389,15 @@ cdef class FvarsHandler: - ``max_terms`` -- maximum number of terms in each entry. Since we use contiguous C-style memory blocks, the size of the block must be known in advance. - - ``use_mp`` -- a boolean indicating whether the array backing - ``self`` should be placed in shared memory. This requires - Python 3.8+, since we must import the + - ``use_mp`` -- an integer indicating the number of child processes + used for multiprocessing. If running serially, use 0. + Multiprocessing requires Python 3.8+, since we must import the ``multiprocessing.shared_memory`` module. Attempting to initialize when ``multiprocessing.shared_memory`` is not available results in an ``ImportError``. + - ``pids_name`` -- the name of a ``ShareableList`` contaning the + process ``pid``'s for every process in the pool (including the + parent process). - ``n_bytes`` -- the number of bytes that should be allocated for each numerator and each denominator stored by the structure. @@ -423,9 +427,15 @@ cdef class FvarsHandler: creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=is_shared_memory_available) + sage: if is_shared_memory_available: + ....: n_proc = f.pool._processes + ....: pids_name = f._pid_list.shm.name + ....: else: + ....: n_proc = 0 + ....: pids_name = None + sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) sage: #In the same shell or in a different shell, attach to fvars - sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name,use_mp=is_shared_memory_available) + sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name,use_mp=n_proc,pids_name=pids_name) sage: if not is_shared_memory_available: ....: fvars2 = fvars sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup @@ -441,8 +451,9 @@ cdef class FvarsHandler: cdef int d = self.field.degree() self.bytes = n_bytes cdef int slots = self.bytes // 8 + cdef int n_proc = use_mp + 1 self.fvars_t = np.dtype([ - ('modified','bool',(1,)), + ('modified',np.int8,(n_proc,)), ('ticks', 'u1', (max_terms,)), ('exp_data', 'u2', (max_terms*30,)), ('coeff_nums',np.int64,(max_terms,d,slots)), @@ -461,8 +472,11 @@ cdef class FvarsHandler: else: self.shm = shared_memory.SharedMemory(name=name) self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) + self.pid_list = shared_memory.ShareableList(name=pids_name) + self.child_id = -1 else: self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t) + self.child_id = 0 #Populate with initialziation data for sextuple, fvar in init_data.items(): if isinstance(fvar, MPolynomial_libsingular): @@ -478,14 +492,9 @@ cdef class FvarsHandler: fvar = tuple(transformed) self[sextuple] = fvar - cdef clear_modified(self): - """ - Reset tagged entries modified by parent process. - """ - self.fvars['modified'][:] = False - @cython.nonecheck(False) @cython.wraparound(False) + @cython.boundscheck(False) def __getitem__(self, sextuple): """ Retrieve a record from the shared memory data structure by @@ -503,7 +512,13 @@ cdef class FvarsHandler: creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=is_shared_memory_available) + sage: if is_shared_memory_available: + ....: n_proc = f.pool._processes + ....: pids_name = f._pid_list.shm.name + ....: else: + ....: n_proc = 0 + ....: pids_name = None + sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f1, f2, f1, f2, f2, f2)] = rhs sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) @@ -530,11 +545,17 @@ cdef class FvarsHandler: if not sextuple in self.sext_to_idx: raise KeyError('Invalid sextuple {}'.format(sextuple)) cdef Py_ssize_t idx = self.sext_to_idx[sextuple] - # if idx in self.obj_cache: - # if self.fvars['modified'][idx]: - # del self.obj_cache[idx] - # else: - # return self.obj_cache[idx] + #Each process builds its own cache, so each process must know + #whether the entry it wants to retrieve has been modified. + #Each process needs to know where to look, so we use an index + #every process agrees on. The pid_list[0] belongs to the main process. + if self.child_id < 0: + self.child_id = self.pid_list.index(getpid()) + if idx in self.obj_cache: + if self.fvars['modified'][idx,self.child_id]: + del self.obj_cache[idx] + else: + return self.obj_cache[idx] cdef ETuple e, exp cdef int count, nnz cdef Integer d, num @@ -548,10 +569,10 @@ cdef class FvarsHandler: cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] cdef np.ndarray[np.int64_t,ndim=3] nums = self.fvars['coeff_nums'][idx] cdef np.ndarray[np.uint64_t,ndim=3] denoms = self.fvars['coeff_denom'][idx] + cdef np.ndarray[np.int8_t,ndim=1] modified = self.fvars['modified'][idx] e = ETuple({}, self.ngens) poly_tup = list() cum = 0 - # for i in range(np.count_nonzero(ticks)): count = np.count_nonzero(ticks) for i in range(count): #Construct new ETuple for each monomial @@ -575,7 +596,9 @@ cdef class FvarsHandler: cyc_coeff = self.field(rats) poly_tup.append((exp, cyc_coeff)) ret = tuple(poly_tup) - # self.obj_cache[idx] = ret + #Cache object and reset modified + self.obj_cache[idx] = ret + modified[self.child_id] = 0 return ret @cython.nonecheck(False) @@ -594,7 +617,13 @@ cdef class FvarsHandler: creating variables fx1..fx27 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=is_shared_memory_available) + sage: if is_shared_memory_available: + ....: n_proc = f.pool._processes + ....: pids_name = f._pid_list.shm.name + ....: else: + ....: n_proc = 0 + ....: pids_name = None + sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f3, f2, f1, f2, f1, f3)] = rhs sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) @@ -624,6 +653,7 @@ cdef class FvarsHandler: cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] cdef np.ndarray[np.int64_t,ndim=3] nums = self.fvars['coeff_nums'][idx] cdef np.ndarray[np.uint64_t,ndim=3] denoms = self.fvars['coeff_denom'][idx] + cdef np.ndarray[np.int8_t,ndim=1] modified = self.fvars['modified'][idx] cdef list digits #Initialize denominators to 1 denoms[:,:,0] = 1 @@ -660,7 +690,7 @@ cdef class FvarsHandler: denoms[i,k,t] = digits[t] k += 1 i += 1 - self.fvars['modified'][idx] = True + modified[:] = 1 def __reduce__(self): """ @@ -672,7 +702,13 @@ cdef class FvarsHandler: sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=is_shared_memory_available) + sage: if is_shared_memory_available: + ....: n_proc = f.pool._processes + ....: pids_name = f._pid_list.shm.name + ....: else: + ....: n_proc = 0 + ....: pids_name = None + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=n_proc,pids_name=pids_name) sage: for s, fvar in loads(dumps(fvars)).items(): ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: @@ -718,7 +754,13 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: from sage.combinat.root_system.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=is_shared_memory_available) + sage: if is_shared_memory_available: + ....: n_proc = f.pool._processes + ....: pids_name = f._pid_list.shm.name + ....: else: + ....: n_proc = 0 + ....: pids_name = None + sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=n_proc,pids_name=pids_name) sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: From 23c9f5fa44ec8d5eacbbacc9f4a926512ac11026 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 21 May 2021 14:35:59 -0400 Subject: [PATCH 087/414] corrections from testing on Python 3.6.5, removed internal tests for shm handlers --- src/sage/combinat/root_system/f_matrix.py | 53 ++++++++++--------- .../fast_parallel_fmats_methods.pyx | 16 +++--- .../combinat/root_system/shm_managers.pyx | 31 ++++++----- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index e87156feebc..0a97d012860 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -302,8 +302,8 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.pool = None #TESTS - self.test_fvars = dict() - self.test_ks = dict() + # self.test_fvars = dict() + # self.test_ks = dict() ####################### ### Class utilities ### @@ -1254,7 +1254,7 @@ def _restore_state(self,filename): return self._fvars, self._solved, self._ks, self.ideal_basis, self._chkpt_status = state #TESTS: - self.test_ks = {i: sq for i, sq in self._ks.items()} + # self.test_ks = {i: sq for i, sq in self._ks.items()} self._update_reduction_params() ################# @@ -1535,18 +1535,18 @@ def get_defining_equations(self,option,output=True): EXAMPLES:: sage: f = FMatrix(FusionRing("B2",1)) - sage: f.get_defining_equations('hexagons') - [fx0 - 1, - fx10^2 + (-zeta32^8)*fx11*fx12 + (-zeta32^12)*fx10, - fx11*fx12 + (-zeta32^8)*fx13^2 + (zeta32^12)*fx13, - fx2 + 1, - fx7 + 1, - fx3*fx8 - fx6, - fx1*fx5 + fx2, + sage: sorted(f.get_defining_equations('hexagons')) + [fx7 + 1, fx6 - 1, - fx4*fx9 + fx7, + fx2 + 1, + fx0 - 1, + fx11*fx12 + (-zeta32^8)*fx13^2 + (zeta32^12)*fx13, + fx10*fx12 + (-zeta32^8)*fx12*fx13 + (zeta32^4)*fx12, fx10*fx11 + (-zeta32^8)*fx11*fx13 + (zeta32^4)*fx11, - fx10*fx12 + (-zeta32^8)*fx12*fx13 + (zeta32^4)*fx12] + fx10^2 + (-zeta32^8)*fx11*fx12 + (-zeta32^12)*fx10, + fx4*fx9 + fx7, + fx3*fx8 - fx6, + fx1*fx5 + fx2] sage: pe = f.get_defining_equations('pentagons') sage: len(pe) 33 @@ -1609,6 +1609,9 @@ def _update_reduction_params(self,eqns=None): sage: f.mp_thresh = 0 sage: if is_shared_memory_available: ....: f._fvars = f._shared_fvars + ....: else: + ....: from sage.combinat.root_system.shm_managers import FvarsHandler + ....: f._fvars = FvarsHandler(f._poly_ring.ngens(),f._field,f._idx_to_sextuple,init_data=f._fvars) sage: f._triangular_elim(verbose=False) # indirect doctest sage: f.ideal_basis [] @@ -1619,13 +1622,13 @@ def _update_reduction_params(self,eqns=None): self._ks.update(eqns) #TESTS: - for i in range(len(eqns)): - eq_tup = eqns[i] - if tup_fixes_sq(eq_tup): - rhs = [-v for v in eq_tup[-1][1]] - self.test_ks[variables(eq_tup)[0]] = rhs - for i, sq in self._ks.items(): - assert sq == self._field(self.test_ks[i]), "{}: OG sq {}, shared sq {}".format(i, sq, self.test_ks[i]) + # for i in range(len(eqns)): + # eq_tup = eqns[i] + # if tup_fixes_sq(eq_tup): + # rhs = [-v for v in eq_tup[-1][1]] + # self.test_ks[variables(eq_tup)[0]] = rhs + # for i, sq in self._ks.items(): + # assert sq == self._field(self.test_ks[i]), "{}: OG sq {}, shared sq {}".format(i, sq, self.test_ks[i]) degs = get_variables_degrees(eqns) if degs: @@ -2203,11 +2206,11 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) #Unzip _fvars and link to shared_memory structure if using multiprocessing #TESTS: - for k, v in self._fvars.items(): - if isinstance(v, tuple): - self.test_fvars[k] = v - else: - self.test_fvars[k] = poly_to_tup(v) + # for k, v in self._fvars.items(): + # if isinstance(v, tuple): + # self.test_fvars[k] = v + # else: + # self.test_fvars[k] = poly_to_tup(v) if use_mp and loads_shared_memory: self._fvars = self._shared_fvars else: diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 029b321ac58..3bdf2d4daf0 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -81,10 +81,10 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): factory._solved[vars[0]] = True linear_terms_exist = True - #TEST: - s = factory._idx_to_sextuple[vars[0]] - factory.test_fvars[s] = tuple() - assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s],factory.test_fvars[s]) + #TESTS: + # s = factory._idx_to_sextuple[vars[0]] + # factory.test_fvars[s] = tuple() + # assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s],factory.test_fvars[s]) if len(eq_tup) == 2: idx = has_appropriate_linear_term(eq_tup) if idx < 0: continue @@ -100,10 +100,10 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): factory._solved[max_var] = True linear_terms_exist = True - #TEST: - s = factory._idx_to_sextuple[max_var] - factory.test_fvars[s] = ((rhs_exp,rhs_coeff),) - assert _unflatten_coeffs(factory._field,factory.test_fvars[s]) == fvars[s], "OG value {}, Shared: {}".format(factory.test_fvars[s],fvars[s]) + #TESTS: + # s = factory._idx_to_sextuple[max_var] + # factory.test_fvars[s] = ((rhs_exp,rhs_coeff),) + # assert _unflatten_coeffs(factory._field,factory.test_fvars[s]) == fvars[s], "OG value {}, Shared: {}".format(factory.test_fvars[s],fvars[s]) return linear_terms_exist cpdef _backward_subs(factory, bint flatten=True): diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index 674ba9fc205..dc73a98f00a 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -84,7 +84,8 @@ cdef class KSHandler: sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available) sage: #In the same shell or in a different shell, attach to fvars - sage: ks2 = KSHandler(n,f._field,name=ks.shm.name,use_mp=is_shared_memory_available) + sage: name = ks.shm.name if is_shared_memory_available else None + sage: ks2 = KSHandler(n,f._field,name=name,use_mp=is_shared_memory_available) sage: if not is_shared_memory_available: ....: ks2 = ks sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup @@ -95,7 +96,7 @@ cdef class KSHandler: ....: Index: 1, square: 4 Index: 3, square: -zeta32^4 + 1/19*zeta32^2 - sage: ks.shm.unlink() + sage: if is_shared_memory_available: ks.shm.unlink() sage: f.shutdown_worker_pool() """ cdef int n, d @@ -278,12 +279,13 @@ cdef class KSHandler: sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available,init_data=f._ks) sage: #In the same shell or in a different one, attach to shared memory handler - sage: k2 = KSHandler(n,f._field,name=ks.shm.name,use_mp=is_shared_memory_available) + sage: name = ks.shm.name if is_shared_memory_available else None + sage: k2 = KSHandler(n,f._field,name=name,use_mp=is_shared_memory_available) sage: if not is_shared_memory_available: ....: k2 = ks sage: ks == k2 True - sage: ks.shm.unlink() + sage: if is_shared_memory_available: ks.shm.unlink() sage: f.shutdown_worker_pool() """ ret = True @@ -352,7 +354,7 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self,n_slots,field,idx_to_sextuple,use_mp=0,pids_name=None,name=None,init_data={},max_terms=20,n_bytes=32): + def __init__(self,n_slots,field,idx_to_sextuple,init_data={},use_mp=0,pids_name=None,name=None,max_terms=20,n_bytes=32): """ Return a shared memory backed dict-like structure to manage the ``_fvars`` attribute of an F-matrix factory object. @@ -435,7 +437,8 @@ cdef class FvarsHandler: ....: pids_name = None sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) sage: #In the same shell or in a different shell, attach to fvars - sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=fvars.shm.name,use_mp=n_proc,pids_name=pids_name) + sage: name = fvars.shm.name if is_shared_memory_available else None + sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=name,use_mp=n_proc,pids_name=pids_name) sage: if not is_shared_memory_available: ....: fvars2 = fvars sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup @@ -443,7 +446,7 @@ cdef class FvarsHandler: sage: fvars[f2, f1, f2, f2, f0, f0] = rhs sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) fx5^5 - sage: fvars.shm.unlink() + sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ self.field = field @@ -532,7 +535,7 @@ cdef class FvarsHandler: True sage: f._tup_to_fpoly(fvars[r]) == -1/19 True - sage: fvars.shm.unlink() + sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() .. NOTE:: @@ -637,7 +640,7 @@ cdef class FvarsHandler: True sage: f._tup_to_fpoly(fvars[r]) == -1/19 True - sage: fvars.shm.unlink() + sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ cdef ETuple exp @@ -671,21 +674,21 @@ cdef class FvarsHandler: k = 0 for r in coeff_tup: num, denom = r.as_integer_ratio() - assert denom != 0, "zero denominator error" + # assert denom != 0, "zero denominator error" if abs(num) > 2**63 or denom > 2**63: print("Large integers encountered in FvarsHandler", num, denom) if abs(num) < 2**63: nums[i,k,0] = num else: digits = num.digits(2**63) - assert len(digits) <= self.bytes // 8, "Numerator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(num,num.nbits()//8+1) + # assert len(digits) <= self.bytes // 8, "Numerator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(num,num.nbits()//8+1) for t in range(len(digits)): nums[i,k,t] = digits[t] if denom < 2**64: denoms[i,k,0] = denom else: digits = denom.digits(2**64) - assert len(digits) <= self.bytes // 8, "Denominator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(denom,denom.nbits()//8+1) + # assert len(digits) <= self.bytes // 8, "Denominator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(denom,denom.nbits()//8+1) for t in range(len(digits)): denoms[i,k,t] = digits[t] k += 1 @@ -712,7 +715,7 @@ cdef class FvarsHandler: sage: for s, fvar in loads(dumps(fvars)).items(): ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: - sage: fvars.shm.unlink() + sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ n = self.fvars.size @@ -764,7 +767,7 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: - sage: fvars.shm.unlink() + sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ return FvarsHandler(n,field,idx_map,init_data=init_data) From 76e2831c9b2d26432140df535022a280ac8e2862 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Mon, 24 May 2021 22:22:55 -0400 Subject: [PATCH 088/414] increased coverage by making tup_fixes_sq method cdef again --- src/sage/combinat/root_system/f_matrix.py | 2 +- src/sage/combinat/root_system/poly_tup_engine.pxd | 3 ++- src/sage/combinat/root_system/poly_tup_engine.pyx | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 0a97d012860..e2abf9ca007 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -36,7 +36,7 @@ _unflatten_coeffs, poly_tup_sortkey, resize, - tup_fixes_sq + # tup_fixes_sq ) from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler from sage.graphs.graph import Graph diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 81c13b94bac..533f2c2d272 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -10,7 +10,8 @@ cpdef list get_variables_degrees(list eqns) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup, field) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) -cpdef bint tup_fixes_sq(tuple eq_tup) +# cpdef bint tup_fixes_sq(tuple eq_tup) +cdef bint tup_fixes_sq(tuple eq_tup) cdef dict subs_squares(dict eq_dict, KSHandler known_sq) cpdef dict compute_known_powers(max_degs, dict val_dict, one) cdef dict subs(tuple poly_tup, dict known_powers, one) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 6ff2f3f1b32..80d8d3073c6 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -123,7 +123,7 @@ cdef inline int has_appropriate_linear_term(tuple eq_tup): Determine whether the given tuple of pairs (of length 2) contains an *appropriate* linear term. - In this context, a linear term is said to be *appropriate* if + In this context, a linear term is said to be *appropriate* if it is in the largest variable in the given polynomial (w.r.t. the degrevlex ordering), the monomial in which the linear term appears is univariate, and the linear term is not a common factor in @@ -319,7 +319,8 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): new_tup.append((exp, coeff_map(coeff))) return tuple(new_tup) -cpdef inline bint tup_fixes_sq(tuple eq_tup): +# cpdef inline bint tup_fixes_sq(tuple eq_tup): +cdef inline bint tup_fixes_sq(tuple eq_tup): r""" Determine if given equation fixes the square of a variable. From 0c7b7f2e104260036ed4290619a848c90806f655 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 25 May 2021 15:36:47 -0400 Subject: [PATCH 089/414] simplified data structures with an eye towards nmf support --- src/sage/combinat/root_system/f_matrix.py | 128 +++++------------- .../fast_parallel_fmats_methods.pyx | 9 +- .../combinat/root_system/poly_tup_engine.pxd | 2 +- .../combinat/root_system/poly_tup_engine.pyx | 12 +- .../combinat/root_system/shm_managers.pyx | 1 - 5 files changed, 43 insertions(+), 109 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index e2abf9ca007..41b2b0ac1ce 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -20,7 +20,7 @@ from copy import deepcopy from ctypes import cast, py_object from itertools import product, zip_longest -from multiprocessing import cpu_count, Pool, set_start_method +from multiprocessing import Pool, cpu_count, set_start_method import numpy as np from os import getpid, remove @@ -283,16 +283,12 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab if inject_variables: print("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) self._poly_ring.inject_variables(get_main_globals()) - self._var_to_sextuple, self._fvars = self.findcases(output=True) - self._var_to_idx = {var: idx for idx, var in enumerate(self._poly_ring.gens())} - self._idx_to_sextuple = {i: self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(self._poly_ring.ngens())} - self._singles = self.singletons() + self._idx_to_sextuple, self._fvars = self.findcases(output=True) #Base field attributes self._field = self._FR.field() r = self._field.defining_polynomial().roots(ring=QQbar, multiplicities=False)[0] self._qqbar_embedding = self._field.hom([r], QQbar) - self._non_cyc_roots = list() #Warm starting self._chkpt_status = -1 @@ -352,7 +348,7 @@ def clear_vars(self): sage: f.get_fvars()[some_key] fx0 """ - self._fvars = {t : fx for fx, t in self._var_to_sextuple.items()} + self._fvars = {t: self._poly_ring.gen(idx) for idx, t in self._idx_to_sextuple.items()} self._solved = list(False for fx in self._fvars) def _reset_solver_state(self): @@ -395,7 +391,8 @@ def _reset_solver_state(self): """ self._FR._basecoer = None self._field = self._FR.field() - self._update_poly_ring_base_field(field=self._field) + self._non_cyc_roots = list() + self._poly_ring = self._poly_ring.change_ring(self._field) self._chkpt_status = -1 self.clear_vars() self.clear_equations() @@ -403,6 +400,7 @@ def _reset_solver_state(self): self._var_degs = [0]*n self._kp = dict() self._ks = KSHandler(n,self._field) + self._singles = self.get_fvars_by_size(1,indices=True) self._nnz = self._get_known_nonz() #Clear relevant caches @@ -410,30 +408,6 @@ def _reset_solver_state(self): self._FR.r_matrix.clear_cache() self._FR.s_ij.clear_cache() - def _update_poly_ring_base_field(self,field): - r""" - Change base field of ``PolynomialRing`` and the corresponding - index attributes - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("D4",1)) - sage: f._update_poly_ring_base_field(QQ) - sage: f._poly_ring.base_ring() == QQ - True - sage: all(fx in f._poly_ring for fx in f._var_to_idx) - True - sage: all(fx in f._poly_ring for fx in f._var_to_sextuple) - True - """ - new_poly_ring = self._poly_ring.change_ring(field) - nvars = self._poly_ring.ngens() - #Do some appropriate conversions - self._singles = [new_poly_ring(fx) for fx in self._singles] - self._var_to_idx = {new_poly_ring.gen(i): i for i in range(nvars)} - self._var_to_sextuple = {new_poly_ring.gen(i): self._var_to_sextuple[self._poly_ring.gen(i)] for i in range(nvars)} - self._poly_ring = new_poly_ring - def fmat(self, a, b, c, d, x, y, data=True): r""" Return the F-Matrix coefficient `(F^{a,b,c}_d)_{x,y}`. @@ -592,11 +566,11 @@ def findcases(self,output=False): sage: f.findcases() 5 sage: f.findcases(output=True) - ({fx4: (t, t, t, t, t, t), - fx3: (t, t, t, t, t, i0), - fx2: (t, t, t, t, i0, t), - fx1: (t, t, t, t, i0, i0), - fx0: (t, t, t, i0, t, t)}, + ({0: (t, t, t, i0, t, t), + 1: (t, t, t, t, i0, i0), + 2: (t, t, t, t, i0, t), + 3: (t, t, t, t, t, i0), + 4: (t, t, t, t, t, t)}, {(t, t, t, i0, t, t): fx0, (t, t, t, t, i0, i0): fx1, (t, t, t, t, i0, t): fx2, @@ -607,48 +581,23 @@ def findcases(self,output=False): if output: idx_map = dict() ret = dict() - # for (a,b,c,d) in list(product(self._FR.basis(), repeat=4)): id_anyon = self._FR.one() for (a,b,c,d) in product(self._FR.basis(), repeat=4): if a == id_anyon or b == id_anyon or c == id_anyon: continue for x in self.f_from(a, b, c, d): for y in self.f_to(a, b, c, d): - # fm = self.fmat(a, b, c, d, x, y, data=False) - # if fm is not None and fm not in [0,1]: if output: - # v = self._poly_ring.gens()[i] v = self._poly_ring.gen(i) ret[(a,b,c,d,x,y)] = v - idx_map[v] = (a, b, c, d, x, y) + # idx_map[v] = (a, b, c, d, x, y) + idx_map[i] = (a, b, c, d, x, y) i += 1 if output: return idx_map, ret else: return i - def singletons(self): - r""" - Find `x_i` that are automatically nonzero, because their F-matrix is - `1 \times 1`. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("E7",1)) - sage: singles = f.singletons() - sage: all(f.fmatrix(*f._var_to_sextuple[v][:4]).nrows() == 1 for v in singles) - True - """ - ret = [] - gens = set(self._poly_ring.gens()) - for (a, b, c, d) in product(self._FR.basis(), repeat=4): - (ff,ft) = (self.f_from(a,b,c,d), self.f_to(a,b,c,d)) - if len(ff) == 1 and len(ft) == 1: - v = self._fvars.get((a,b,c,d,ff[0],ft[0]), None) - if v in gens: - ret.append(v) - return ret - def f_from(self,a,b,c,d): r""" Return the possible `x` such that there are morphisms @@ -936,7 +885,7 @@ def _get_known_nonz(self): (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) """ - nonz = {self._var_to_idx[var] : 100 for var in self._singles} + nonz = {idx: 100 for idx in self._singles} for idx, v in self._ks.items(): nonz[idx] = 100 return ETuple(nonz, self._poly_ring.ngens()) @@ -1630,13 +1579,8 @@ def _update_reduction_params(self,eqns=None): # for i, sq in self._ks.items(): # assert sq == self._field(self.test_ks[i]), "{}: OG sq {}, shared sq {}".format(i, sq, self.test_ks[i]) - degs = get_variables_degrees(eqns) - if degs: - for i, d in enumerate(degs): - self._var_degs[i] = d - else: - for i in range(self._poly_ring.ngens()): - self._var_degs[i] = 0 + for i, d in enumerate(get_variables_degrees(eqns,self._poly_ring.ngens())): + self._var_degs[i] = d self._nnz = self._get_known_nonz() self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) @@ -1666,17 +1610,14 @@ def _triangular_elim(self,eqns=None,verbose=True): sage: f.ideal_basis [] """ - ret = True if eqns is None: eqns = self.ideal_basis - ret = False - # using_mp = self.pool is not None + while True: linear_terms_exist = _solve_for_linear_terms(self,eqns) if not linear_terms_exist: break _backward_subs(self) - #Compute new reduction params and update eqns self._update_reduction_params(eqns=eqns) # n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) @@ -1693,8 +1634,6 @@ def _triangular_elim(self,eqns=None,verbose=True): eqns.sort(key=poly_tup_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) - if ret: - return eqns self.ideal_basis = eqns ##################### @@ -1815,7 +1754,7 @@ def _partition_eqns(self,eqns=None,verbose=True): print(graph.connected_components_sizes()) return partition - def _par_graph_gb(self,eqns=None,term_order="degrevlex",verbose=True): + def _par_graph_gb(self,eqns=None,term_order="degrevlex",largest_comp=60,verbose=True): r""" Compute a Groebner basis for a list of equations partitioned according to their corresponding graph. @@ -1861,7 +1800,7 @@ def _par_graph_gb(self,eqns=None,term_order="degrevlex",verbose=True): for comp, comp_eqns in self._partition_eqns(verbose=verbose).items(): #Check if component is too large to process - if len(comp) > 60: + if len(comp) > largest_comp: # fmat_size = 0 # #For informative print statement # for i in range(1,nmax+1): @@ -2010,7 +1949,6 @@ def _get_explicit_solution(self,eqns=None,verbose=True): for fx, rhs in self._ks.items(): if not self._solved[fx]: lt = (ETuple({fx : 2},n), one) - # eqns.append((lt, (ETuple({},n), -self._field(list(rhs))))) eqns.append(((lt, (ETuple({},n), -rhs)))) eqns_partition = self._partition_eqns(verbose=verbose) @@ -2064,9 +2002,8 @@ def _get_explicit_solution(self,eqns=None,verbose=True): numeric_fvars = {k : phi(v) for k, v in numeric_fvars.items()} for i, elt in enumerate(bf_elts): numeric_fvars[non_cyclotomic_roots[i][0]] = elt - #Update polynomial ring - self._update_poly_ring_base_field(self._field) + self._poly_ring = self._poly_ring.change_ring(self._field) #Ensure all F-symbols are known for fx in numeric_fvars: @@ -2181,9 +2118,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start the F-symbols is determined based on the ``CartanType`` associated to ``self``. See :meth:`attempt_number_field_computation` for details. """ - self._reset_solver_state() if self._poly_ring.ngens() == 0: return + self._reset_solver_state() #Resume computation from checkpoint if warm_start: @@ -2226,7 +2163,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: print("Hex elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) - self._checkpoint(checkpoint,2,verbose=verbose) if self._chkpt_status < 3: @@ -2236,7 +2172,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) - self._checkpoint(checkpoint,3,verbose=verbose) #Simplify and eliminate variables @@ -2245,7 +2180,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: print("Pent elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) - self._checkpoint(checkpoint,4,verbose=verbose) #Try adding degrevlex gb -> elim loop until len(ideal_basis) does not change @@ -2255,16 +2189,13 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self.ideal_basis = self._par_graph_gb(term_order="lex",verbose=verbose) self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(verbose=verbose) - self._checkpoint(checkpoint,5,verbose=verbose) #Find numeric values for each F-symbol self._get_explicit_solution(verbose=verbose) - - #The calculation was successful, so we may delete checkpoints + #The calculation was successful, so we may delete checkpoints and shared resources self._chkpt_status = 7 self.clear_equations() - #Close worker pool and destroy shared resources self.shutdown_worker_pool() if checkpoint: remove("fmatrix_solver_checkpoint_"+self.get_fr_str()+".pickle") @@ -2288,6 +2219,7 @@ def _fix_gauge(self, algorithm=""): sage: f = FMatrix(FusionRing("A3",1)) # long time sage: f._reset_solver_state() # long time + sage: f._var_to_sextuple = {f._poly_ring.gen(i): s for i, s in f._idx_to_sextuple.items()} # long time sage: eqns = f.get_defining_equations("hexagons")+f.get_defining_equations("pentagons") # long time sage: f.ideal_basis = set(Ideal(eqns).groebner_basis()) # long time sage: _, _ = f._substitute_degree_one() # long time @@ -2322,6 +2254,7 @@ def _substitute_degree_one(self, eqns=None): creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() + sage: f._var_to_sextuple = {f._poly_ring.gen(i): s for i, s in f._idx_to_sextuple.items()} sage: f.ideal_basis = [fx0 - 8, fx4**2 - 3, fx4 + fx10 + 3, fx4 + fx9] sage: _, _ = f._substitute_degree_one() sage: f._fvars[f._var_to_sextuple[fx0]] @@ -2335,17 +2268,18 @@ def _substitute_degree_one(self, eqns=None): new_knowns = set() useless = set() for eq in eqns: - if eq.degree() == 1 and sum(eq.degrees()) <= 2 and not self._solved[self._var_to_idx[eq.lm()]]: + if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self._solved: self._fvars[self._var_to_sextuple[eq.lm()]] = -sum(c * m for c, m in zip(eq.coefficients()[1:], eq.monomials()[1:])) / eq.lc() #Add variable to set of known values and remove this equation new_knowns.add(eq.lm()) useless.add(eq) #Update fvars depending on other variables - for fx in new_knowns: - self._solved[self._var_to_idx[fx]] = fx + for idx, fx in enumerate(self._poly_ring.gens()): + if fx in new_knowns: + self._solved[idx] = fx for sextuple, rhs in self._fvars.items(): - d = {var: self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if self._solved[self._var_to_idx[var]]} + d = {var: self._fvars[self._var_to_sextuple[var]] for var in rhs.variables() if var in self._solved} if d: self._fvars[sextuple] = rhs.subs(d) return new_knowns, useless @@ -2360,6 +2294,7 @@ def _update_equations(self): creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() + sage: f._var_to_sextuple = {f._poly_ring.gen(i): s for i, s in f._idx_to_sextuple.items()} sage: f.ideal_basis = [fx0 - 8, fx4 + fx9, fx4**2 + fx3 - fx9**2] sage: _, _ = f._substitute_degree_one() sage: f._update_equations() @@ -2424,9 +2359,10 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o sage: f.get_defining_equations("pentagons") [] """ - self._reset_solver_state() if self._poly_ring.ngens() == 0: return + self._reset_solver_state() + self._var_to_sextuple = {self._poly_ring.gen(i): s for i, s in self._idx_to_sextuple.items()} if equations is None: if verbose: diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 3bdf2d4daf0..7f6013dfdb9 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -154,15 +154,18 @@ cpdef _backward_subs(factory, bint flatten=True): one = factory._field.one() _ks = factory._ks fvars = factory._fvars - idx_to_sextuple = factory._idx_to_sextuple solved = factory._solved - for i in range(len(idx_to_sextuple)-1,-1,-1): + cdef dict idx_to_sextuple = factory._idx_to_sextuple + cdef int nvars = len(idx_to_sextuple) + # for i in range(len(idx_to_sextuple)-1,-1,-1): + for i in range(nvars-1,-1,-1): sextuple = idx_to_sextuple[i] rhs = fvars[sextuple] d = {var_idx: fvars[idx_to_sextuple[var_idx]] for var_idx in variables(rhs) if solved[var_idx]} if d: - kp = compute_known_powers(get_variables_degrees([rhs]), d, one) + # kp = compute_known_powers(get_variables_degrees([rhs]), d, one) + kp = compute_known_powers(get_variables_degrees([rhs],nvars), d, one) res = tuple(subs_squares(subs(rhs,kp,one),_ks).items()) if flatten: res = _flatten_coeffs(res) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/combinat/root_system/poly_tup_engine.pxd index 533f2c2d272..f89cd31c713 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/combinat/root_system/poly_tup_engine.pxd @@ -6,7 +6,7 @@ from sage.rings.polynomial.polydict cimport ETuple cpdef tuple poly_to_tup(MPolynomial_libsingular poly) cpdef MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_libsingular parent) cpdef tuple resize(tuple eq_tup, dict idx_map, int nvars) -cpdef list get_variables_degrees(list eqns) +cpdef list get_variables_degrees(list eqns, int nvars) cpdef list variables(tuple eq_tup) cpdef constant_coeff(tuple eq_tup, field) cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map) diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/combinat/root_system/poly_tup_engine.pyx index 80d8d3073c6..9345853b20e 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/combinat/root_system/poly_tup_engine.pyx @@ -232,7 +232,7 @@ cdef inline ETuple degrees(tuple poly_tup): max_degs = max_degs.emax( ( poly_tup[i])[0]) return max_degs -cpdef list get_variables_degrees(list eqns): +cpdef list get_variables_degrees(list eqns, int nvars): r""" Find maximum degrees for each variable in equations. @@ -242,11 +242,11 @@ cpdef list get_variables_degrees(list eqns): sage: R. = PolynomialRing(QQ) sage: polys = [x**2 + 1, x*y*z**2 - 4*x*y, x*z**3 - 4/3*y + 1] sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: get_variables_degrees([poly_to_tup(p) for p in polys]) + sage: get_variables_degrees([poly_to_tup(p) for p in polys], 3) [2, 1, 3] """ if not eqns: - return list() + return [0]*nvars cdef ETuple max_deg cdef int i max_deg = degrees(eqns[0]) @@ -342,7 +342,6 @@ cdef inline bint tup_fixes_sq(tuple eq_tup): ### Simplification ### ###################### -# cdef dict subs_squares(dict eq_dict, known_sq): cdef dict subs_squares(dict eq_dict, KSHandler known_sq): r""" Substitute for known squares into a given polynomial. @@ -366,10 +365,7 @@ cdef dict subs_squares(dict eq_dict, KSHandler known_sq): for exp, coeff in eq_dict.items(): new_e = dict() for idx, power in exp.sparse_iter(): - # if idx in known_sq: if known_sq.contains(idx): - # coeff *= known_sq[idx] ** (power // 2) - # coeff *= pow(known_sq[idx], power // 2) coeff *= pow(known_sq.get(idx), power // 2) #New power is 1 if power is odd if power & True: @@ -464,7 +460,7 @@ cpdef dict compute_known_powers(max_degs, dict val_dict, one): sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees - sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys]) + sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys], 3) sage: compute_known_powers(max_deg, known_val, R.base_ring().one()) {0: [(((0, 0, 0), 1),), (((0, 0, 0), -1),), diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/combinat/root_system/shm_managers.pyx index dc73a98f00a..b1f0c450096 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/combinat/root_system/shm_managers.pyx @@ -674,7 +674,6 @@ cdef class FvarsHandler: k = 0 for r in coeff_tup: num, denom = r.as_integer_ratio() - # assert denom != 0, "zero denominator error" if abs(num) > 2**63 or denom > 2**63: print("Large integers encountered in FvarsHandler", num, denom) if abs(num) < 2**63: From b5f6783f704686d02172f6c85af941a7e0121af3 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 25 May 2021 19:27:46 -0400 Subject: [PATCH 090/414] removed some code that was commented out --- src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 7f6013dfdb9..1f634985fcc 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -157,14 +157,12 @@ cpdef _backward_subs(factory, bint flatten=True): solved = factory._solved cdef dict idx_to_sextuple = factory._idx_to_sextuple cdef int nvars = len(idx_to_sextuple) - # for i in range(len(idx_to_sextuple)-1,-1,-1): for i in range(nvars-1,-1,-1): sextuple = idx_to_sextuple[i] rhs = fvars[sextuple] d = {var_idx: fvars[idx_to_sextuple[var_idx]] for var_idx in variables(rhs) if solved[var_idx]} if d: - # kp = compute_known_powers(get_variables_degrees([rhs]), d, one) kp = compute_known_powers(get_variables_degrees([rhs],nvars), d, one) res = tuple(subs_squares(subs(rhs,kp,one),_ks).items()) if flatten: @@ -321,7 +319,6 @@ cdef get_reduced_pentagons(factory, tuple mp_params): #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) - # cdef dict fvars = factory._fvars #Handle both cyclotomic and orthogonal solution method cdef bint must_zip_up for k, v in factory._fvars.items(): From c82aefc4d9ab894b972d8daffdf5afaacf6591c0 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Wed, 26 May 2021 16:36:13 -0400 Subject: [PATCH 091/414] E83 is multiplicity free --- src/sage/combinat/root_system/f_matrix.py | 8 ++++++-- .../fast_parallel_fmats_methods.pyx | 20 +++++++++---------- src/sage/combinat/root_system/fusion_ring.py | 7 ++++++- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 41b2b0ac1ce..2bed456f3e2 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -349,7 +349,7 @@ def clear_vars(self): fx0 """ self._fvars = {t: self._poly_ring.gen(idx) for idx, t in self._idx_to_sextuple.items()} - self._solved = list(False for fx in self._fvars) + self._solved = [False]*self._poly_ring.ngens() def _reset_solver_state(self): r""" @@ -1798,7 +1798,7 @@ def _par_graph_gb(self,eqns=None,term_order="degrevlex",largest_comp=60,verbose= # for i in range(nmax+1): # vars_by_size.append(self.get_fvars_by_size(i)) - for comp, comp_eqns in self._partition_eqns(verbose=verbose).items(): + for comp, comp_eqns in self._partition_eqns(eqns=eqns,verbose=verbose).items(): #Check if component is too large to process if len(comp) > largest_comp: # fmat_size = 0 @@ -1905,6 +1905,8 @@ def attempt_number_field_computation(self): if ct.letter == 'E': if ct.n < 8 and k == 2: return False + if ct.n == 8 and k == 3: + return False if ct.letter == 'F' and k == 2: return False if ct.letter == 'G' and k == 2: @@ -2021,6 +2023,8 @@ def _get_explicit_solution(self,eqns=None,verbose=True): #Update base field attributes self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() + if self._FR._basecoer: + self._FR.r_matrix.clear_cache() def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start="", use_mp=True, verbose=True): r""" diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx index 1f634985fcc..883bf90c76d 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx @@ -152,9 +152,9 @@ cpdef _backward_subs(factory, bint flatten=True): 1 """ one = factory._field.one() - _ks = factory._ks fvars = factory._fvars solved = factory._solved + cdef KSHandler _ks = factory._ks cdef dict idx_to_sextuple = factory._idx_to_sextuple cdef int nvars = len(idx_to_sextuple) for i in range(nvars-1,-1,-1): @@ -252,18 +252,18 @@ cdef get_reduced_hexagons(factory, tuple mp_params): #Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) cdef dict fvars - cdef bint must_zip_up + cdef bint must_zip_up = False if not output: fvars = {s: factory._poly_ring.gen(i) for i, s in factory._idx_to_sextuple.items()} else: - #Handle both cyclotomic and orthogonal solution method - for k, v in factory._fvars.items(): - must_zip_up = isinstance(v, tuple) - break - if must_zip_up: - fvars = {k: _tup_to_poly(v,parent=factory._poly_ring) for k, v in factory._fvars.items()} - else: - fvars = factory._fvars + #Handle both cyclotomic and orthogonal solution method + for k, v in factory._fvars.items(): + must_zip_up = isinstance(v, tuple) + break + if must_zip_up: + fvars = {k: _tup_to_poly(v,parent=factory._poly_ring) for k, v in factory._fvars.items()} + else: + fvars = factory._fvars r_matrix = factory._FR.r_matrix _Nk_ij = factory._FR.Nk_ij id_anyon = factory._FR.one() diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/combinat/root_system/fusion_ring.py index 9555869a73d..67df34abe07 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/combinat/root_system/fusion_ring.py @@ -1071,12 +1071,17 @@ def is_multiplicity_free(self): if ct.n == 1: return True return k <= 2 - if ct.letter in ['B','D','G','F','E']: + # if ct.letter in ['B','D','G','F','E']: + if ct.letter in ['B','D','F','G']: return k <= 2 if ct.letter == 'C': if ct.n == 2: return k <= 2 return k == 1 + if ct.letter == 'E': + if ct.n == 8: + return k <= 3 + return k <= 2 ################################### ### Braid group representations ### From 13deb9670abad544bfc18e3e59d534dd8f81ce9a Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Fri, 28 May 2021 14:45:13 -0700 Subject: [PATCH 092/414] f_matrix.py docstring: remember E8 at level 3 is multiplicity free --- src/sage/combinat/root_system/f_matrix.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/combinat/root_system/f_matrix.py index 2bed456f3e2..228b6f13f90 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/combinat/root_system/f_matrix.py @@ -102,7 +102,9 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab +------------------------+----------+ | `D_r, r\geq 4` | `\leq 2` | +------------------------+----------+ - | `G_2,F_4,E_r` | `\leq 2` | + | `G_2,F_4,E_6,E_7` | `\leq 2` | + +------------------------+----------+ + | `E_8` | `\leq 3` | +------------------------+----------+ Beyond this limitation, computation of the F-matrix From c2e8f44a09a862f98b032df9ec80382c0bd40cb5 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 29 Jun 2021 11:10:36 +1000 Subject: [PATCH 093/414] Finishing review, including moving fusion ring files to subfolder of algebras. --- .../en/reference/algebras/fusion_rings.rst | 20 ++ src/doc/en/reference/algebras/index.rst | 1 + .../media/fusiontree.png | Bin .../media/fusiontree.tex | 0 src/doc/en/reference/combinat/module_list.rst | 2 - src/sage/algebras/all.py | 10 +- src/sage/algebras/fusion_rings/__init__.py | 0 src/sage/algebras/fusion_rings/all.py | 21 ++ .../fusion_rings}/f_matrix.py | 259 +++++++++--------- .../fast_parallel_fmats_methods.pxd | 0 .../fast_parallel_fmats_methods.pyx | 27 +- .../fast_parallel_fusion_ring_braid_repn.pxd | 0 .../fast_parallel_fusion_ring_braid_repn.pyx | 212 +++++++------- .../fusion_rings}/fusion_ring.py | 32 ++- .../fusion_rings}/poly_tup_engine.pxd | 2 +- .../fusion_rings}/poly_tup_engine.pyx | 78 +++--- .../fusion_rings}/shm_managers.pxd | 0 .../fusion_rings}/shm_managers.pyx | 92 ++++--- src/sage/combinat/root_system/__init__.py | 4 +- src/sage/combinat/root_system/all.py | 3 - 20 files changed, 416 insertions(+), 347 deletions(-) create mode 100644 src/doc/en/reference/algebras/fusion_rings.rst rename src/doc/en/reference/{combinat => algebras}/media/fusiontree.png (100%) rename src/doc/en/reference/{combinat => algebras}/media/fusiontree.tex (100%) create mode 100644 src/sage/algebras/fusion_rings/__init__.py create mode 100644 src/sage/algebras/fusion_rings/all.py rename src/sage/{combinat/root_system => algebras/fusion_rings}/f_matrix.py (91%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/fast_parallel_fmats_methods.pxd (100%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/fast_parallel_fmats_methods.pyx (94%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/fast_parallel_fusion_ring_braid_repn.pxd (100%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/fast_parallel_fusion_ring_braid_repn.pyx (60%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/fusion_ring.py (98%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/poly_tup_engine.pxd (95%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/poly_tup_engine.pyx (87%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/shm_managers.pxd (100%) rename src/sage/{combinat/root_system => algebras/fusion_rings}/shm_managers.pyx (90%) diff --git a/src/doc/en/reference/algebras/fusion_rings.rst b/src/doc/en/reference/algebras/fusion_rings.rst new file mode 100644 index 00000000000..06d2473c558 --- /dev/null +++ b/src/doc/en/reference/algebras/fusion_rings.rst @@ -0,0 +1,20 @@ +Fusion Rings +============ + +.. toctree:: + :maxdepth: 2 + + sage/algebras/fusion_rings/fusion_ring + sage/algebras/fusion_rings/f_matrix + +F-Matrix Backend +---------------- + +.. toctree:: + :maxdepth: 2 + + sage/algebras/fusion_rings/fast_parallel_fmats_methods + sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn + sage/algebras/fusion_rings/poly_tup_engine + sage/algebras/fusion_rings/shm_managers + diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index 1a415c7c337..2860b133467 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -47,6 +47,7 @@ Named associative algebras sage/algebras/clifford_algebra sage/algebras/cluster_algebra sage/combinat/descent_algebra + fusion_rings sage/algebras/hall_algebra sage/combinat/posets/incidence_algebras sage/algebras/group_algebra diff --git a/src/doc/en/reference/combinat/media/fusiontree.png b/src/doc/en/reference/algebras/media/fusiontree.png similarity index 100% rename from src/doc/en/reference/combinat/media/fusiontree.png rename to src/doc/en/reference/algebras/media/fusiontree.png diff --git a/src/doc/en/reference/combinat/media/fusiontree.tex b/src/doc/en/reference/algebras/media/fusiontree.tex similarity index 100% rename from src/doc/en/reference/combinat/media/fusiontree.tex rename to src/doc/en/reference/algebras/media/fusiontree.tex diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index b546fdb81fb..5ac1f5f01d5 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -291,8 +291,6 @@ Comprehensive Module list sage/combinat/root_system/weight_lattice_realizations sage/combinat/root_system/weight_space sage/combinat/root_system/weyl_characters - sage/combinat/root_system/fusion_ring - sage/combinat/root_system/f_matrix sage/combinat/root_system/weyl_group sage/combinat/rooted_tree sage/combinat/rsk diff --git a/src/sage/algebras/all.py b/src/sage/algebras/all.py index 671282406c6..ac06fa76ab8 100644 --- a/src/sage/algebras/all.py +++ b/src/sage/algebras/all.py @@ -20,18 +20,18 @@ import sage.algebras.catalog as algebras -from .quantum_groups.all import * from .quatalg.all import * +from .steenrod.all import * +from .fusion_rings.all import * +from .lie_algebras.all import * +from .quantum_groups.all import * +from .lie_conformal_algebras.all import * # Algebra base classes from .algebra import Algebra from .free_algebra import FreeAlgebra from .free_algebra_quotient import FreeAlgebraQuotient -from .steenrod.all import * -from .lie_algebras.all import * -from .quantum_groups.all import * -from .lie_conformal_algebras.all import * from .finite_dimensional_algebras.all import FiniteDimensionalAlgebra diff --git a/src/sage/algebras/fusion_rings/__init__.py b/src/sage/algebras/fusion_rings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/algebras/fusion_rings/all.py b/src/sage/algebras/fusion_rings/all.py new file mode 100644 index 00000000000..6742afd5d55 --- /dev/null +++ b/src/sage/algebras/fusion_rings/all.py @@ -0,0 +1,21 @@ +""" +Fusion Rings +""" + +# **************************************************************************** +# Copyright (C) 2021 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.algebras.fusion_rings.fusion_ring', ['FusionRing']) +lazy_import('sage.algebras.fusion_rings.f_matrix', ['FMatrix']) + +del lazy_import + diff --git a/src/sage/combinat/root_system/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py similarity index 91% rename from src/sage/combinat/root_system/f_matrix.py rename to src/sage/algebras/fusion_rings/f_matrix.py index 228b6f13f90..60375e7d49b 100644 --- a/src/sage/combinat/root_system/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -24,11 +24,11 @@ import numpy as np from os import getpid, remove -from sage.combinat.root_system.fast_parallel_fmats_methods import ( +from sage.algebras.fusion_rings.fast_parallel_fmats_methods import ( _backward_subs, _solve_for_linear_terms, executor ) -from sage.combinat.root_system.poly_tup_engine import ( +from sage.algebras.fusion_rings.poly_tup_engine import ( apply_coeff_map, constant_coeff, compute_known_powers, get_variables_degrees, variables, @@ -38,7 +38,7 @@ resize, # tup_fixes_sq ) -from sage.combinat.root_system.shm_managers import KSHandler, FvarsHandler +from sage.algebras.fusion_rings.shm_managers import KSHandler, FvarsHandler from sage.graphs.graph import Graph from sage.matrix.constructor import matrix from sage.misc.misc import get_main_globals @@ -197,7 +197,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab ([i0, p], [i0, p]) The last two statments show that the possible values of - `X` and `Y` when `A=B=C=D=s` are `i_0` and `p`. + `X` and `Y` when `A = B = C = D = s` are `i_0` and `p`. The F-matrix is computed by solving the so-called pentagon and hexagon equations. The *pentagon equations* @@ -257,7 +257,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab cyclotomic field that is the base ring of the underlying :class:`FusionRing`, but sometimes in an extension field adjoining some square roots. When this happens, the :class:`FusionRing` is - modified, adding an attribute :attr:`_basecoer` that is + modified, adding an attribute ``_basecoer`` that is a coercion from the cyclotomic field to the field containing the F-matrix. The field containing the F-matrix is available through :meth:`field`. @@ -278,7 +278,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab if inject_variables and (self._FR._fusion_labels is None): self._FR.fusion_labels(fusion_label, inject_variables=True) if not self._FR.is_multiplicity_free(): - raise ValueError("FMatrix is only available for multiplicity free FusionRings") + raise NotImplementedError("FMatrix is only available for multiplicity free FusionRings") #Set up F-symbols entry by entry n_vars = self.findcases() self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) @@ -311,7 +311,7 @@ def __repr__(self): """ EXAMPLES:: - sage: FMatrix(FusionRing("B2",1)) + sage: FMatrix(FusionRing("B2", 1)) F-Matrix factory for The Fusion Ring of Type B2 and level 1 with Integer Ring coefficients """ return "F-Matrix factory for %s"%self._FR @@ -322,7 +322,7 @@ def clear_equations(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("E6",1)) + sage: f = FMatrix(FusionRing("E6", 1)) sage: f.get_defining_equations('hexagons', output=False) sage: len(f.ideal_basis) 6 @@ -360,7 +360,7 @@ def _reset_solver_state(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: f._reset_solver_state() sage: K = f.field() sage: len(f._nnz.nonzero_positions()) @@ -416,7 +416,7 @@ def fmat(self, a, b, c, d, x, y, data=True): EXAMPLES:: - sage: f=FMatrix(FusionRing("G2",1,fusion_labels=("i0","t"),inject_variables=True)) + sage: f=FMatrix(FusionRing("G2", 1, fusion_labels=("i0","t"), inject_variables=True)) sage: [f.fmat(t,t,t,t,x,y) for x in f._FR.basis() for y in f._FR.basis()] [fx1, fx2, fx3, fx4] sage: f.find_cyclotomic_solution(output=True) @@ -476,7 +476,7 @@ def fmatrix(self,a,b,c,d): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",2,fusion_labels="c",inject_variables=True)) + sage: f = FMatrix(FusionRing("A1", 2, fusion_labels="c", inject_variables=True)) sage: f.fmatrix(c1,c1,c1,c1) [fx0 fx1] [fx2 fx3] @@ -506,15 +506,15 @@ def field(self): The field may change after running :meth:`find_orthogonal_solution`. At that point, this method could return the associated :class:`FusionRing`'s cyclotomic field, an - appropriate :class:`NumberField` that was computed on the fly - by the F-matrix solver, or the :class:`AlgebraicField` ``QQbar``. + appropriate :func:`NumberField` that was computed on the fly + by the F-matrix solver, or the :class:`QQbar`. Depending on the ``CartanType`` of ``self``, the solver may need to compute an extension field containing certain square roots that do not belong to the associated :class:`FusionRing`'s cyclotomic field. - In certain cases we revert to ``QQbar`` because the extension field - computation does not seem to terminate. See + In certain cases we revert to :class:`QQbar` because + the extension field computation does not seem to terminate. See :meth:`attempt_number_field_computation` for more details. The method :meth:`get_non_cyclotomic_roots` returns a list of @@ -536,7 +536,7 @@ def field(self): .. NOTE:: Consider using ``self.field().optimized_representation()`` to - obtain an equivalent :class:`NumberField` with a defining + obtain an equivalent :func:`NumberField` with a defining polynomial with smaller coefficients, for a more efficient element representation. """ @@ -564,7 +564,7 @@ def findcases(self,output=False): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1,fusion_labels=("i0","t"))) + sage: f = FMatrix(FusionRing("G2", 1, fusion_labels=("i0","t"))) sage: f.findcases() 5 sage: f.findcases(output=True) @@ -635,16 +635,16 @@ def f_to(self,a,b,c,d): EXAMPLES:: - sage: b22 = FusionRing("B2",2) - sage: b22.fusion_labels("b",inject_variables=True) + sage: b22 = FusionRing("B2", 2) + sage: b22.fusion_labels("b", inject_variables=True) sage: B=FMatrix(b22) - sage: B.fmatrix(b2,b4,b2,b4) + sage: B.fmatrix(b2, b4, b2, b4) [fx266 fx267 fx268] [fx269 fx270 fx271] [fx272 fx273 fx274] - sage: B.f_from(b2,b4,b2,b4) + sage: B.f_from(b2, b4, b2, b4) [b1, b3, b5] - sage: B.f_to(b2,b4,b2,b4) + sage: B.f_to(b2, b4, b2, b4) [b1, b3, b5] """ return [y for y in self._FR.basis() @@ -666,7 +666,7 @@ def get_fvars(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) + sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.get_fvars()[(f1, f1, f1, f0, f2, f2)] @@ -683,7 +683,7 @@ def get_poly_ring(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("B6",1)) + sage: f = FMatrix(FusionRing("B6", 1)) sage: f.get_poly_ring() Multivariate Polynomial Ring in fx0, ..., fx13 over Cyclotomic Field of order 96 and degree 32 @@ -693,8 +693,9 @@ def get_poly_ring(self): def get_non_cyclotomic_roots(self): r""" Return a list of roots that define the extension of the associated - :class:`FusionRing`'s base :class:`CyclotomicField` containing all - the F-symbols. + :class:`FusionRing`'s base + :class:`Cyclotomic field`, + containing all the F-symbols. OUTPUT: @@ -706,13 +707,13 @@ def get_non_cyclotomic_roots(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("E6",1)) + sage: f = FMatrix(FusionRing("E6", 1)) sage: f.find_orthogonal_solution(verbose=False) sage: f.field() == f.FR().field() True sage: f.get_non_cyclotomic_roots() [] - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: f.find_orthogonal_solution(verbose=False) sage: f.field() == f.FR().field() False @@ -722,23 +723,25 @@ def get_non_cyclotomic_roots(self): When ``self.field()`` is a ``NumberField``, one may use :meth:`get_qqbar_embedding` to embed the resulting values into - ``QQbar``. + :class:`QQbar`. """ return sorted(set(self._non_cyc_roots)) def get_qqbar_embedding(self): r""" Return an embedding from the base field containing F-symbols (the - associated :class:`FusionRing`'s :class:`CyclotomicField`, a - :class:`NumberField`, or ``QQbar``) into ``QQbar``. + associated :class:`FusionRing`'s + :class:`Cyclotomic field`, + a :func:`NumberField`, or :class:`QQbar`) into + :class:`QQbar`. This embedding is useful for getting a better sense for the F-symbols, particularly when they are computed as elements of a - :class:`NumberField`. See also :meth:`get_non_cyclotomic_roots`. + :func:`NumberField`. See also :meth:`get_non_cyclotomic_roots`. EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1), fusion_label="g", inject_variables=True) + sage: f = FMatrix(FusionRing("G2", 1), fusion_label="g", inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: f.find_orthogonal_solution() @@ -765,17 +768,18 @@ def get_coerce_map_from_fr_cyclotomic_field(self): r""" Return a coercion map from the associated :class:`FusionRing`'s cyclotomic field into the base field containing all F-symbols - (this could be the :class:`FusionRing`'s :class:`CyclotomicField`, a - :class:`NumberField`, or ``QQbar``). + (this could be the :class:`FusionRing`'s + :class:`Cyclotomic field`, + a :func:`NumberField`, or :class:`QQbar`). EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: f.find_orthogonal_solution(verbose=False) sage: f.FR().field() Cyclotomic Field of order 60 and degree 16 sage: f.field() - Number Field in a with defining polynomial y^32 - 6*y^30 - 7*y^28 + 62*y^26 - 52*y^24 - 308*y^22 + 831*y^20 + 7496*y^18 + 18003*y^16 - 2252*y^14 + 42259*y^12 - 65036*y^10 + 29368*y^8 - 3894*y^6 + 377*y^4 - 22*y^2 + 1 + Number Field in a with defining polynomial y^32 - ... - 22*y^2 + 1 sage: phi = f.get_coerce_map_from_fr_cyclotomic_field() sage: phi.domain() == f.FR().field() True @@ -783,13 +787,14 @@ def get_coerce_map_from_fr_cyclotomic_field(self): True When F-symbols are computed as elements of the associated - :class:`FusionRing`'s base :class:`CyclotomicField`, + :class:`FusionRing`'s base + :class:`Cyclotomic field`, we have ``self.field() == self.FR().field()`` and this method returns the identity map on ``self.field()``. :: - sage: f = FMatrix(FusionRing("A2",1)) + sage: f = FMatrix(FusionRing("A2", 1)) sage: f.find_orthogonal_solution(verbose=False) sage: phi = f.get_coerce_map_from_fr_cyclotomic_field() sage: f.field() @@ -812,14 +817,15 @@ def get_coerce_map_from_fr_cyclotomic_field(self): def get_fvars_in_alg_field(self): r""" - Return F-symbols as elements of the :class:`AlgebraicField` ``QQbar``. + Return F-symbols as elements of the :class:`QQbar`. + This method uses the embedding defined by :meth:`get_qqbar_embedding` to coerce - F-symbols into ``QQbar``. + F-symbols into :class:`QQbar`. EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1), fusion_label="g", inject_variables=True) + sage: f = FMatrix(FusionRing("G2", 1), fusion_label="g", inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: f.find_orthogonal_solution(verbose=False) @@ -840,7 +846,7 @@ def get_radical_expression(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: f.FR().fusion_labels("g", inject_variables=True) sage: f.find_orthogonal_solution(verbose=False) sage: radical_fvars = f.get_radical_expression() # long time (~1.5s) @@ -860,7 +866,7 @@ def _get_known_vals(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D4",1)) + sage: f = FMatrix(FusionRing("D4", 1)) sage: f._reset_solver_state() sage: len(f._get_known_vals()) == 0 True @@ -881,7 +887,7 @@ def _get_known_nonz(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D5",1)) # indirect doctest + sage: f = FMatrix(FusionRing("D5", 1)) # indirect doctest sage: f._reset_solver_state() sage: f._nnz (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, @@ -902,7 +908,7 @@ def largest_fmat_size(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("B3",2)) + sage: f = FMatrix(FusionRing("B3", 2)) sage: f.largest_fmat_size() 4 """ @@ -916,8 +922,7 @@ def get_fvars_by_size(self,n,indices=False): INPUT: - `n` -- a positive integer - - - ``indices`` -- (default: ``False``) a boolean. + - ``indices`` -- boolean (default: ``False``) If ``indices`` is ``False`` (default), this method returns a set of sextuples `(a,b,c,d,x,y)` identifying @@ -989,7 +994,7 @@ def save_fvars(self, filename): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2",1)) + sage: f = FMatrix(FusionRing("A2", 1)) sage: f.find_orthogonal_solution(verbose=False) sage: fvars = f.get_fvars() sage: K = f.field() @@ -1057,7 +1062,7 @@ def get_fr_str(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("B3",1)) + sage: f = FMatrix(FusionRing("B3", 1)) sage: f.get_fr_str() 'B31' """ @@ -1070,14 +1075,14 @@ def _checkpoint(self, do_chkpt, status, verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",3)) + sage: f = FMatrix(FusionRing("A1", 3)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons',output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: f._triangular_elim(verbose=False) @@ -1108,11 +1113,11 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons',output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: f._fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params() sage: f.get_defining_equations('pentagons',output=False) @@ -1121,7 +1126,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f._checkpoint(do_chkpt=True,status=4) Checkpoint 4 reached! sage: del f - sage: f = FMatrix(FusionRing("A1",2)) + sage: f = FMatrix(FusionRing("A1", 2)) sage: f.find_orthogonal_solution(warm_start="fmatrix_solver_checkpoint_A12.pickle") Computing F-symbols for The Fusion Ring of Type A1 and level 2 with Integer Ring coefficients with 14 variables... Partitioned 0 equations into 0 components of size: @@ -1142,23 +1147,23 @@ def _checkpoint(self, do_chkpt, status, verbose=True): if verbose: print(f"Checkpoint {status} reached!") - def _restore_state(self,filename): + def _restore_state(self, filename): r""" Load solver state from file. Use this method both for warm-starting :meth:`find_orthogonal_solution` and to load pickled results. EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",2)) + sage: f = FMatrix(FusionRing("A1", 2)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: f.get_defining_equations('hexagons',output=False) + sage: f.get_defining_equations('hexagons', output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: f._fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params() sage: fvars = f._fvars @@ -1166,10 +1171,10 @@ def _restore_state(self,filename): sage: solved = f._solved sage: ks = f._ks sage: status = f._chkpt_status - sage: f._checkpoint(do_chkpt=True,status=2) + sage: f._checkpoint(do_chkpt=True, status=2) Checkpoint 2 reached! sage: del f - sage: f = FMatrix(FusionRing("A1",2)) + sage: f = FMatrix(FusionRing("A1", 2)) sage: f._reset_solver_state() sage: f._restore_state("fmatrix_solver_checkpoint_A12.pickle") sage: for sextuple, fvar in fvars.items(): @@ -1187,10 +1192,10 @@ def _restore_state(self,filename): TESTS:: - sage: f = FMatrix(FusionRing("A1",3)) + sage: f = FMatrix(FusionRing("A1", 3)) sage: f.find_orthogonal_solution(save_results="test.pickle",verbose=False) # long time sage: del f - sage: f = FMatrix(FusionRing("A1",3)) + sage: f = FMatrix(FusionRing("A1", 3)) sage: f.find_orthogonal_solution(warm_start="test.pickle") # long time sage: f._chkpt_status == 7 # long time True @@ -1212,7 +1217,7 @@ def _restore_state(self,filename): ### MapReduce ### ################# - def start_worker_pool(self,processes=None): + def start_worker_pool(self, processes=None): """ Initialize a ``multiprocessing`` worker pool for parallel processing, which may be used e.g. to set up defining equations using @@ -1246,7 +1251,7 @@ def start_worker_pool(self,processes=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: he = f.get_defining_equations('hexagons') sage: sorted(he) @@ -1330,7 +1335,7 @@ def shutdown_worker_pool(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",3)) + sage: f = FMatrix(FusionRing("A1", 3)) sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: he = f.get_defining_equations('hexagons') sage: f.shutdown_worker_pool() @@ -1345,7 +1350,7 @@ def shutdown_worker_pool(self): self._pid_list.shm.unlink() del self.__dict__['_shared_fvars'] - def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_thresh=None): + def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, mp_thresh=None): r""" Apply the given mapper to each element of the given input iterable and return the results (with no duplicates) in a list. @@ -1367,7 +1372,7 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",2)) + sage: f = FMatrix(FusionRing("A1", 2)) sage: f._reset_solver_state() sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1,False)])) 11 @@ -1409,7 +1414,7 @@ def _map_triv_reduce(self,mapper,input_iter,worker_pool=None,chunksize=None,mp_t ### Equations set up ### ######################## - def get_orthogonality_constraints(self,output=True): + def get_orthogonality_constraints(self, output=True): r""" Get equations imposed on the F-matrix by orthogonality. @@ -1454,7 +1459,7 @@ def get_orthogonality_constraints(self,output=True): return eqns self.ideal_basis.extend([poly_to_tup(eq) for eq in eqns]) - def get_defining_equations(self,option,output=True): + def get_defining_equations(self, option, output=True): r""" Get the equations defining the ideal generated by the hexagon or pentagon relations. @@ -1485,7 +1490,7 @@ def get_defining_equations(self,option,output=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("B2",1)) + sage: f = FMatrix(FusionRing("B2", 1)) sage: sorted(f.get_defining_equations('hexagons')) [fx7 + 1, fx6 - 1, @@ -1518,7 +1523,7 @@ def get_defining_equations(self,option,output=True): ### Equations processing ### ############################ - def _tup_to_fpoly(self,eq_tup): + def _tup_to_fpoly(self, eq_tup): r""" Assemble a polynomial object from its tuple representation. @@ -1529,12 +1534,12 @@ def _tup_to_fpoly(self,eq_tup): It is meant for internal use by the F-matrix solver. This method is a left inverse of - :meth:`sage.combinat.root_system.poly_tup_engine.poly_to_tup`. + :meth:`sage.algebras.fusion_rings.poly_tup_engine.poly_to_tup`. EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup - sage: f = FMatrix(FusionRing("C3",1)) + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup + sage: f = FMatrix(FusionRing("C3", 1)) sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: he = f.get_defining_equations('hexagons') sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) @@ -1543,25 +1548,25 @@ def _tup_to_fpoly(self,eq_tup): """ return _tup_to_poly(eq_tup,parent=self._poly_ring) - def _update_reduction_params(self,eqns=None): + def _update_reduction_params(self, eqns=None): r""" Update reduction parameters that are solver state attributes. EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",3)) + sage: f = FMatrix(FusionRing("A1", 3)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: f.get_defining_equations('hexagons',output=False) + sage: f.get_defining_equations('hexagons', output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 sage: if is_shared_memory_available: ....: f._fvars = f._shared_fvars ....: else: - ....: from sage.combinat.root_system.shm_managers import FvarsHandler + ....: from sage.algebras.fusion_rings.shm_managers import FvarsHandler ....: f._fvars = FvarsHandler(f._poly_ring.ngens(),f._field,f._idx_to_sextuple,init_data=f._fvars) sage: f._triangular_elim(verbose=False) # indirect doctest sage: f.ideal_basis @@ -1586,7 +1591,7 @@ def _update_reduction_params(self,eqns=None): self._nnz = self._get_known_nonz() self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) - def _triangular_elim(self,eqns=None,verbose=True): + def _triangular_elim(self, eqns=None, verbose=True): r""" Perform triangular elimination of linear terms in two-term equations until no such terms exist. @@ -1598,15 +1603,15 @@ def _triangular_elim(self,eqns=None,verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3",1)) - sage: f.get_defining_equations('hexagons',output=False) + sage: f = FMatrix(FusionRing("D3", 1)) + sage: f.get_defining_equations('hexagons', output=False) sage: f.get_orthogonality_constraints(output=False) sage: gb = f._par_graph_gb(verbose=False) - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis = sorted(gb, key=poly_tup_sortkey) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: f._fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: f._triangular_elim() Elimination epoch completed... 0 eqns remain in ideal basis sage: f.ideal_basis @@ -1642,7 +1647,7 @@ def _triangular_elim(self,eqns=None,verbose=True): ### Graph methods ### ##################### - def equations_graph(self,eqns=None): + def equations_graph(self, eqns=None): r""" Construct a graph corresponding to the given equations. @@ -1680,7 +1685,7 @@ def equations_graph(self,eqns=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("A3",1)) + sage: f = FMatrix(FusionRing("A3", 1)) sage: f.get_poly_ring().ngens() 27 sage: he = f.get_defining_equations('hexagons') @@ -1711,7 +1716,7 @@ def equations_graph(self,eqns=None): G.add_edge(x,y) return G - def _partition_eqns(self,eqns=None,verbose=True): + def _partition_eqns(self, eqns=None, verbose=True): r""" Partition equations corresponding to edges in a disconnected graph. @@ -1729,7 +1734,7 @@ def _partition_eqns(self,eqns=None,verbose=True): sage: partition = f._partition_eqns() Partitioned 11 equations into 5 components of size: [4, 3, 3, 3, 1] - sage: from sage.combinat.root_system.poly_tup_engine import variables + sage: from sage.algebras.fusion_rings.poly_tup_engine import variables sage: for c, e in partition.items(): ....: assert set(v for eq_tup in e for v in variables(eq_tup)) == set(c) sage: vars_in_partition = set() @@ -1756,7 +1761,7 @@ def _partition_eqns(self,eqns=None,verbose=True): print(graph.connected_components_sizes()) return partition - def _par_graph_gb(self,eqns=None,term_order="degrevlex",largest_comp=60,verbose=True): + def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=60, verbose=True): r""" Compute a Groebner basis for a list of equations partitioned according to their corresponding graph. @@ -1780,7 +1785,7 @@ def _par_graph_gb(self,eqns=None,term_order="degrevlex",largest_comp=60,verbose= sage: gb = f._par_graph_gb() Partitioned 10 equations into 2 components of size: [4, 1] - sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs + sage: from sage.algebras.fusion_rings.poly_tup_engine import _unflatten_coeffs sage: ret = [f._tup_to_fpoly(_unflatten_coeffs(f.field(), t)) for t in gb] sage: ret.sort(); ret [fx4 + (-zeta80^24 + zeta80^16), @@ -1812,8 +1817,8 @@ def _par_graph_gb(self,eqns=None,term_order="degrevlex",largest_comp=60,verbose= temp_eqns.extend(comp_eqns) else: small_comps.append(comp_eqns) - input_iter = zip_longest(small_comps,[],fillvalue=term_order) - small_comp_gb = self._map_triv_reduce('compute_gb',input_iter,worker_pool=self.pool,chunksize=1,mp_thresh=50) + input_iter = zip_longest(small_comps, [], fillvalue=term_order) + small_comp_gb = self._map_triv_reduce('compute_gb', input_iter, worker_pool=self.pool, chunksize=1, mp_thresh=50) ret = small_comp_gb + temp_eqns return ret @@ -1830,18 +1835,18 @@ def _get_component_variety(self,var,eqns): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",2)) + sage: f = FMatrix(FusionRing("G2", 2)) sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: f.get_defining_equations('hexagons',output=False) # long time + sage: f.get_defining_equations('hexagons', output=False) # long time sage: f.shutdown_worker_pool() sage: partition = f._partition_eqns() # long time Partitioned 327 equations into 35 components of size: [27, 27, 27, 24, 24, 16, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 9, 9, 6, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1] sage: c = (216, 292, 319) - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: eqns = partition[c] + [poly_to_tup(f._poly_ring.gen(216)-1)] # long time - sage: f._get_component_variety(c,eqns) # long time + sage: f._get_component_variety(c, eqns) # long time [{216: -1, 292: -1, 319: 1}] """ #Define smaller poly ring in component vars @@ -1866,23 +1871,23 @@ def attempt_number_field_computation(self): r""" Based on the ``CartanType`` of ``self`` and data known on March 17, 2021, determine whether to attempt - to find a :class:`NumberField` containing all the F-symbols. + to find a :func:`NumberField` containing all the F-symbols. This method is used by :meth:`find_orthogonal_solution` to determine a field containing all F-symbols. See :meth:`field` and :meth:`get_non_cyclotomic_roots`. - For certain :class:`FusionRing `, the number field + For certain :class:`fusion rings `, the number field computation does not terminate in reasonable time. In these cases, we report F-symbols as elements - of the :class:`AlgebraicField` ``QQbar``. + of the :class:`QQbar`. EXAMPLES:: - sage: f = FMatrix(FusionRing("F4",2)) + sage: f = FMatrix(FusionRing("F4", 2)) sage: f.attempt_number_field_computation() False - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: f.attempt_number_field_computation() True @@ -1890,10 +1895,10 @@ def attempt_number_field_computation(self): In certain cases, F-symbols are found in the associated :class:`FusionRing`'s cyclotomic field and a - :class:`NumberField` computation is not needed. In these + :func:`NumberField` computation is not needed. In these cases this method returns ``True`` but the :meth:`find_orthogonal_solution` solver does *not* - undertake a :class:`NumberField` computation. + undertake a :func:`NumberField` computation. """ ct = self._FR.cartan_type() k = self._FR._k @@ -1924,7 +1929,7 @@ def _get_explicit_solution(self,eqns=None,verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",3)) # indirect doctest + sage: f = FMatrix(FusionRing("A1", 3)) # indirect doctest sage: f.find_orthogonal_solution() # long time Computing F-symbols for The Fusion Ring of Type A1 and level 3 with Integer Ring coefficients with 71 variables... Set up 134 hex and orthogonality constraints... @@ -2084,7 +2089,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start EXAMPLES:: - sage: f = FMatrix(FusionRing("B5",1), fusion_label="b", inject_variables=True) + sage: f = FMatrix(FusionRing("B5", 1), fusion_label="b", inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: f.find_orthogonal_solution() @@ -2119,10 +2124,15 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start True In any case, the F-symbols are obtained as elements of the associated - :class:`FusionRing`'s :class:`CyclotomicField`, a computed - :class:`NumberField`, or ``QQbar``. Currently, the field containing - the F-symbols is determined based on the ``CartanType`` associated - to ``self``. See :meth:`attempt_number_field_computation` for details. + :class:`FusionRing`'s + :class:`Cyclotomic field`, + a computed :func:`NumberField`, or :class:`QQbar`. + Currently, the field containing the F-symbols is determined based + on the ``CartanType`` associated to ``self``. + + .. SEEALSO:: + + :meth:`attempt_number_field_computation` """ if self._poly_ring.ngens() == 0: return @@ -2223,7 +2233,7 @@ def _fix_gauge(self, algorithm=""): EXAMPLES:: - sage: f = FMatrix(FusionRing("A3",1)) # long time + sage: f = FMatrix(FusionRing("A3", 1)) sage: f._reset_solver_state() # long time sage: f._var_to_sextuple = {f._poly_ring.gen(i): s for i, s in f._idx_to_sextuple.items()} # long time sage: eqns = f.get_defining_equations("hexagons")+f.get_defining_equations("pentagons") # long time @@ -2256,7 +2266,7 @@ def _substitute_degree_one(self, eqns=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -2296,7 +2306,7 @@ def _update_equations(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -2333,7 +2343,7 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o EXAMPLES:: - sage: f = FMatrix(FusionRing("A2",1,fusion_labels="a",inject_variables=True),inject_variables=True) + sage: f = FMatrix(FusionRing("A2", 1, fusion_labels="a", inject_variables=True), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.find_cyclotomic_solution(output=True) @@ -2408,8 +2418,8 @@ def certify_pentagons(self,use_mp=True,verbose=False): EXAMPLES:: - sage: f = FMatrix(FusionRing("C3",1)) # long time - sage: f.find_orthogonal_solution() # long time + sage: f = FMatrix(FusionRing("C3", 1)) + sage: f.find_orthogonal_solution() # long time Computing F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients with 71 variables... Set up 134 hex and orthogonality constraints... Partitioned 134 equations into 17 components of size: @@ -2457,7 +2467,7 @@ def fmats_are_orthogonal(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D4",1)) + sage: f = FMatrix(FusionRing("D4", 1)) sage: f.find_orthogonal_solution(verbose=False) sage: f.fmats_are_orthogonal() True @@ -2474,7 +2484,7 @@ def fvars_are_real(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1",3)) # long time + sage: f = FMatrix(FusionRing("A1", 3)) sage: f.find_orthogonal_solution(verbose=False) # long time sage: f.fvars_are_real() # long time True @@ -2483,6 +2493,7 @@ def fvars_are_real(self): for k, v in self._fvars.items(): AA(self._qqbar_embedding(v)) except ValueError: - print("The F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) + print("the F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) return False return True + diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd similarity index 100% rename from src/sage/combinat/root_system/fast_parallel_fmats_methods.pxd rename to src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd diff --git a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx similarity index 94% rename from src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx rename to src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx index 883bf90c76d..c3158585904 100644 --- a/src/sage/combinat/root_system/fast_parallel_fmats_methods.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx @@ -9,7 +9,7 @@ Fast F-Matrix methods # **************************************************************************** cimport cython -from sage.combinat.root_system.poly_tup_engine cimport ( +from sage.algebras.fusion_rings.poly_tup_engine cimport ( compute_known_powers, get_variables_degrees, variables, poly_to_tup, _tup_to_poly, @@ -18,7 +18,7 @@ from sage.combinat.root_system.poly_tup_engine cimport ( has_appropriate_linear_term, resize ) -from sage.combinat.root_system.shm_managers cimport KSHandler, FvarsHandler +from sage.algebras.fusion_rings.shm_managers cimport KSHandler, FvarsHandler from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple @@ -44,14 +44,14 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): Defining fx0, ..., fx26 sage: f._reset_solver_state() sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _solve_for_linear_terms + sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import _solve_for_linear_terms sage: _solve_for_linear_terms(f) True sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[0]]) @@ -118,14 +118,14 @@ cpdef _backward_subs(factory, bint flatten=True): Defining fx0, ..., fx26 sage: f._reset_solver_state() sage: f.ideal_basis = [fx0**3, fx0 + fx3**4, fx2**2 - fx3, fx2 - fx3**2, fx4 - fx2] - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: f.ideal_basis = [poly_to_tup(p) for p in f.ideal_basis] - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _solve_for_linear_terms + sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import _solve_for_linear_terms sage: _solve_for_linear_terms(f) True sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[0]]) @@ -140,7 +140,7 @@ cpdef _backward_subs(factory, bint flatten=True): sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: _solve_for_linear_terms(f) True - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import _backward_subs + sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import _backward_subs sage: _backward_subs(f) sage: f._tup_to_fpoly(f._fvars[f._idx_to_sextuple[0]]) 0 @@ -457,7 +457,7 @@ cdef dict mappers = { cpdef executor(tuple params): r""" Execute a function defined in this module - (``sage.combinat.root_system.fast_parallel_fmats_methods``) in a worker + (``sage.algebras.fusion_rings.fast_parallel_fmats_methods``) in a worker process, and supply the factory parameter by constructing a reference to the ``FMatrix`` object in the worker's memory adress space from its ``id``. @@ -479,7 +479,7 @@ cpdef executor(tuple params): TESTS:: - sage: from sage.combinat.root_system.fast_parallel_fmats_methods import executor + sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import executor sage: fmats = FMatrix(FusionRing("A1",3)) sage: fmats._reset_solver_state() sage: params = (('get_reduced_hexagons', id(fmats)), (0,1,True)) @@ -539,3 +539,4 @@ cdef pent_verify(factory, tuple mp_params): feq_verif(factory,worker_results,fvars,Nk_ij,id_anyon,nonuple) if i % 50000000 == 0 and i and verbose: print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) + diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd similarity index 100% rename from src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pxd rename to src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd diff --git a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx similarity index 60% rename from src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx rename to src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx index 4270243eb26..562e44fb6c9 100644 --- a/src/sage/combinat/root_system/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx @@ -10,7 +10,7 @@ Fast FusionRing methods for computing braid group representations from ctypes import cast, py_object cimport cython -from sage.combinat.root_system.fast_parallel_fmats_methods cimport _fmat +from sage.algebras.fusion_rings.fast_parallel_fmats_methods cimport _fmat from itertools import product from sage.rings.qqbar import QQbar @@ -37,8 +37,9 @@ cdef mid_sig_ij(fusion_ring,row,col,a,b): xi, yi = row xj, yj = col entry = 0 - for c in fusion_ring.basis(): - for d in fusion_ring.basis(): + cdef list basis = list(fusion_ring.basis()) + for c in basis: + for d in basis: ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) f1 = _fmat(_fvars,_Nk_ij,one,a,a,yi,b,xi,c) f2 = _fmat(_fvars,_Nk_ij,one,a,a,a,c,d,yi) @@ -100,7 +101,7 @@ cdef cached_odd_one_out_ij(fusion_ring,xi,xj,a,b): @cython.cdivision(True) cdef sig_2k(fusion_ring, tuple args): r""" - Compute entries of the 2k-th braid generator + Compute entries of the `2k`-th braid generator """ #Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars @@ -114,62 +115,65 @@ cdef sig_2k(fusion_ring, tuple args): cdef list worker_results = list() #Get computational basis cdef list comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) - cdef dict basis_dict = { elt : i for i, elt in enumerate(comp_basis) } + cdef dict basis_dict = {elt: i for i, elt in enumerate(comp_basis)} cdef int dim = len(comp_basis) cdef set coords = set() cdef int i #Avoid pickling cyclotomic field element objects cdef bint must_flatten_coeff = fusion_ring.fvars_field() != QQbar + cdef list basis = list(fusion_ring.basis()) for i in range(dim): - for f,e,q in product(fusion_ring.basis(), repeat=3): - #Distribute work amongst processes - ctr += 1 - if ctr % n_proc != child_id: - continue - - #Compute appropriate possible nonzero row index - nnz_pos = list(comp_basis[i]) - nnz_pos[k-1] = f - nnz_pos[k] = e - #Handle the special case k = 1 - if k > 1: - nnz_pos[n_strands//2+k-2] = q - nnz_pos = tuple(nnz_pos) - - #Skip repeated entries when k = 1 - if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in coords: - m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] - #A few special cases - top_left = m[0] - if k >= 3: - top_left = l[k-3] - root = b - if k - 1 < len(l): - root = l[k-1] - - #Handle the special case k = 1 - if k == 1: - entry = cached_mid_sig_ij(fusion_ring,m[:2],(f,e),a,root) - - #Avoid pickling cyclotomic field element objects - if must_flatten_coeff: - entry = entry.list() - - worker_results.append(((basis_dict[nnz_pos],i), entry)) - coords.add((basis_dict[nnz_pos],i)) - continue - - entry = 0 - for p in fusion_ring.basis(): - f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[k-1],m[k],root,l[k-2],p) - f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,e,root,q,p) - entry += f1 * cached_mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 - - #Avoid pickling cyclotomic field element objects - if must_flatten_coeff: - entry = entry.list() - - worker_results.append(((basis_dict[nnz_pos], i), entry)) + for f in basis: + for e in basis: + for q in basis: + #Distribute work amongst processes + ctr += 1 + if ctr % n_proc != child_id: + continue + + #Compute appropriate possible nonzero row index + nnz_pos = list(comp_basis[i]) + nnz_pos[k-1] = f + nnz_pos[k] = e + #Handle the special case k = 1 + if k > 1: + nnz_pos[n_strands//2+k-2] = q + nnz_pos = tuple(nnz_pos) + + #Skip repeated entries when k = 1 + if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in coords: + m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] + #A few special cases + top_left = m[0] + if k >= 3: + top_left = l[k-3] + root = b + if k - 1 < len(l): + root = l[k-1] + + #Handle the special case k = 1 + if k == 1: + entry = cached_mid_sig_ij(fusion_ring,m[:2],(f,e),a,root) + + #Avoid pickling cyclotomic field element objects + if must_flatten_coeff: + entry = entry.list() + + worker_results.append(((basis_dict[nnz_pos],i), entry)) + coords.add((basis_dict[nnz_pos],i)) + continue + + entry = 0 + for p in fusion_ring.basis(): + f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[k-1],m[k],root,l[k-2],p) + f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,e,root,q,p) + entry += f1 * cached_mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 + + #Avoid pickling cyclotomic field element objects + if must_flatten_coeff: + entry = entry.list() + + worker_results.append(((basis_dict[nnz_pos], i), entry)) return worker_results @cython.nonecheck(False) @@ -184,63 +188,68 @@ cdef odd_one_out(fusion_ring, tuple args): _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() - cdef list worker_results = list() - cdef int child_id, n_proc + cdef list worker_results = [] + cdef list nnz_pos_temp + cdef tuple nnz_pos + cdef int child_id, n_proc, i child_id, n_proc, fn_args = args a, b, n_strands = fn_args cdef int ctr = -1 #Get computational basis - comp_basis = fusion_ring.get_computational_basis(a, b, n_strands) - basis_dict = {elt: i for i, elt in enumerate(comp_basis)} - dim = len(comp_basis) + cdef list comp_basis = fusion_ring.get_computational_basis(a, b, n_strands) + cdef dict basis_dict = {elt: i for i, elt in enumerate(comp_basis)} + cdef int dim = len(comp_basis) #Avoid pickling cyclotomic field element objects cdef bint must_flatten_coeff = fusion_ring.fvars_field() != QQbar + + cdef list basis = list(fusion_ring.basis()) for i in range(dim): - for f, q in product(fusion_ring.basis(),repeat=2): - #Distribute work amongst processes - ctr += 1 - if ctr % n_proc != child_id: - continue - - #Compute appropriate possible nonzero row index - nnz_pos = list(comp_basis[i]) - nnz_pos[n_strands//2-1] = f - #Handle small special case - if n_strands > 3: - nnz_pos[-1] = q - nnz_pos = tuple(nnz_pos) - - if nnz_pos in comp_basis: - m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] - - #Handle a couple of small special cases - if n_strands == 3: - entry = cached_odd_one_out_ij(fusion_ring,m[-1],f,a,b) + for f in basis: + for q in basis: + #Distribute work amongst processes + ctr += 1 + if ctr % n_proc != child_id: + continue + + #Compute appropriate possible nonzero row index + nnz_pos_temp = list(comp_basis[i]) + nnz_pos_temp[n_strands//2-1] = f + #Handle small special case + if n_strands > 3: + nnz_pos_temp[-1] = q + nnz_pos = tuple(nnz_pos_temp) + + if nnz_pos in comp_basis: + m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] + + #Handle a couple of small special cases + if n_strands == 3: + entry = cached_odd_one_out_ij(fusion_ring,m[-1],f,a,b) + + #Avoid pickling cyclotomic field element objects + if must_flatten_coeff: + entry = entry.list() + + worker_results.append(((basis_dict[nnz_pos], i), entry)) + continue + top_left = m[0] + if n_strands > 5: + top_left = l[-2] + root = b + + #Compute relevant entry + entry = 0 + for p in basis: + f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[-1],a,root,l[-1],p) + f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,a,root,q,p) + entry += f1 * cached_odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 #Avoid pickling cyclotomic field element objects if must_flatten_coeff: entry = entry.list() - worker_results.append(((basis_dict[nnz_pos], i), entry)) - continue - top_left = m[0] - if n_strands > 5: - top_left = l[-2] - root = b - - #Compute relevant entry - entry = 0 - for p in fusion_ring.basis(): - f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[-1],a,root,l[-1],p) - f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,a,root,q,p) - entry += f1 * cached_odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 - - #Avoid pickling cyclotomic field element objects - if must_flatten_coeff: - entry = entry.list() - - worker_results.append(((basis_dict[nnz_pos],i), entry)) + worker_results.append(((basis_dict[nnz_pos],i), entry)) return worker_results ############################## @@ -270,14 +279,14 @@ cpdef executor(tuple params): TESTS:: - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor + sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import executor sage: FR = FusionRing("A1",4) sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) # long time sage: len(executor(params)) == 13 # long time True - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import executor + sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import executor sage: FR = FusionRing("A1",2) sage: FR.fusion_labels("a",inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) @@ -305,7 +314,7 @@ cpdef _unflatten_entries(fusion_ring, list entries): EXAMPLES:: - sage: from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import _unflatten_entries + sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import _unflatten_entries sage: fr = FusionRing("B2",2) sage: F = fr.field() sage: coeff = [F.random_element() for i in range(2)] @@ -319,3 +328,4 @@ cpdef _unflatten_entries(fusion_ring, list entries): if F != QQbar: for i, (coord, entry) in enumerate(entries): entries[i] = (coord, F(entry)) + diff --git a/src/sage/combinat/root_system/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py similarity index 98% rename from src/sage/combinat/root_system/fusion_ring.py rename to src/sage/algebras/fusion_rings/fusion_ring.py index 67df34abe07..868843ced25 100644 --- a/src/sage/combinat/root_system/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -14,7 +14,7 @@ from itertools import product, zip_longest from multiprocessing import Pool, set_start_method from sage.combinat.q_analogues import q_int -from sage.combinat.root_system.fast_parallel_fusion_ring_braid_repn import ( +from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import ( executor, _unflatten_entries ) @@ -526,8 +526,8 @@ def fvars_field(self): Depending on the ``CartanType`` associated to ``self`` and whether a call to an F-matrix solver has been made, this method - will return the same field as :meth:`field`, a ``NumberField``, - or the ``AlgebraicField`` ``QQbar``. + will return the same field as :meth:`field`, a :func:`NumberField`, + or the :class:`QQbar`. See :meth:`FMatrix.attempt_number_field_computation` for more details. Before running an F-matrix solver, the output of this method matches @@ -560,9 +560,10 @@ def fvars_field(self): sage: A13.field() Cyclotomic Field of order 40 and degree 16 - In some cases, the :meth:`NumberField.optimized_representation` + In some cases, the :meth:`NumberField.optimized_representation() + ` may be used to obtain a better defining polynomial for the - computed ``NumberField``. + computed :func:`NumberField`. """ if self.is_multiplicity_free(): return self.fmats.field() @@ -968,7 +969,9 @@ def r_matrix(self, i, j, k, base_coercion=True): def global_q_dimension(self, base_coercion=True): r""" Return `\sum d_i^2`, where the sum is over all simple objects - and `d_i` is the quantum dimension. It is a positive real number. + and `d_i` is the quantum dimension. + + The global `q`-dimension is a positive real number. EXAMPLES:: @@ -982,9 +985,11 @@ def global_q_dimension(self, base_coercion=True): def total_q_order(self, base_coercion=True): r""" - Return the positive square root of ``self.global_q_dimension()`` - as an element of ``self.field()``. Implemented as `D_{+}e^{-i\pi c/4}` - where `D_+` is ``self.D_plus()`` and `c` is ``self.virasoro_central_charge()`` + Return the positive square root of :meth:`self.global_q_dimension() + ` as an element of :meth:`self.field() `. + + This is implemented as `D_{+}e^{-i\pi c/4}`, where `D_+` is + :meth:`D_plus()` and `c` is :meth:`virasoro_central_charge()`. EXAMPLES:: @@ -1028,7 +1033,6 @@ def D_plus(self, base_coercion=True): return ret return self._basecoer(ret) - def D_minus(self, base_coercion=True): r""" Return `\sum d_i^2\theta_i^{-1}` where `i` runs through the simple @@ -1117,7 +1121,7 @@ def get_computational_basis(self,a,b,n_strands): l_{k-2} \in l_{k-3} \otimes m_{k-1},\\ \cdots,\\ l_2 \in l_1 \otimes m_3,\\ - l_1 \in m_1 \otimes m_2. + l_1 \in m_1 \otimes m_2, \end{array} where `z \in x \otimes y` means `N_{xy}^z \neq 0`. @@ -1168,7 +1172,7 @@ def fmats(self): sage: A15.fmats F-Matrix factory for The Fusion Ring of Type A1 and level 5 with Integer Ring coefficients """ - from sage.combinat.root_system.f_matrix import FMatrix + from sage.algebras.fusion_rings.f_matrix import FMatrix return FMatrix(self) def _emap(self, mapper, input_args, worker_pool=None): @@ -1261,7 +1265,7 @@ def get_braid_generators(self, to be verbose with the computation For more information on the optional parameters, see - :meth:`find_orthogonal_solution` of the :class:`FMatrix` module. + :meth:`FMatrix.find_orthogonal_solution`. Given a simple object in the fusion category, here called ``fusing_anyon`` allowing the universal R-matrix to act on adjacent @@ -1279,7 +1283,7 @@ def get_braid_generators(self, is as follows. See :meth:`get_computational_basis` for more information. - .. image:: ../../../media/fusiontree.png + .. IMAGE:: ../../../media/fusiontree.png ``sig`` is a list of braid group generators as matrices. In some cases these will be represented as sparse matrices. diff --git a/src/sage/combinat/root_system/poly_tup_engine.pxd b/src/sage/algebras/fusion_rings/poly_tup_engine.pxd similarity index 95% rename from src/sage/combinat/root_system/poly_tup_engine.pxd rename to src/sage/algebras/fusion_rings/poly_tup_engine.pxd index f89cd31c713..a4ddf9b92ae 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pxd +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pxd @@ -1,4 +1,4 @@ -from sage.combinat.root_system.shm_managers cimport KSHandler +from sage.algebras.fusion_rings.shm_managers cimport KSHandler from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, MPolynomialRing_libsingular from sage.rings.polynomial.polydict cimport ETuple diff --git a/src/sage/combinat/root_system/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx similarity index 87% rename from src/sage/combinat/root_system/poly_tup_engine.pyx rename to src/sage/algebras/fusion_rings/poly_tup_engine.pyx index 9345853b20e..f0e277921c2 100644 --- a/src/sage/combinat/root_system/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -19,7 +19,7 @@ cpdef inline tuple poly_to_tup(MPolynomial_libsingular poly): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: R. = PolynomialRing(QQ) sage: poly_to_tup(x**2 + 1) (((2, 0), 1), ((0, 0), 1)) @@ -49,20 +49,20 @@ cpdef inline MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_ EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import _tup_to_poly + sage: from sage.algebras.fusion_rings.poly_tup_engine import _tup_to_poly sage: K = CyclotomicField(20) sage: R. = PolynomialRing(K) sage: poly_tup = (((2,0),K.one()), ((0,0),K.one())) sage: _tup_to_poly(poly_tup, parent=R) x^2 + 1 sage: poly = x**2*y**4 - 4/5*x*y**2 + 1/3 * y - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: _tup_to_poly(poly_to_tup(poly), parent=R) == poly True TESTS:: - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, _tup_to_poly + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup, _tup_to_poly sage: R. = PolynomialRing(CyclotomicField(20)) sage: r = R.random_element() sage: _tup_to_poly(poly_to_tup(r), parent=R) == r @@ -98,10 +98,10 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import _unflatten_coeffs + sage: from sage.algebras.fusion_rings.poly_tup_engine import _unflatten_coeffs sage: fm = FMatrix(FusionRing("A2",2)) sage: p = fm._poly_ring.random_element() - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: flat_poly_tup = list() sage: for exp, cyc_coeff in poly_to_tup(p): ....: flat_poly_tup.append((exp, tuple(cyc_coeff._coefficients()))) @@ -134,7 +134,7 @@ cdef inline int has_appropriate_linear_term(tuple eq_tup): If the given polynomial contains an appropriate linear term, this method returns the index of the monomial in which the term appears. - Otherwise, the method returns -1. + Otherwise, the method returns `-1`. """ max_var = degrees(eq_tup).nonzero_positions()[0] cdef ETuple m @@ -159,7 +159,7 @@ cpdef inline tup_to_univ_poly(tuple eq_tup, univ_poly_ring): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import tup_to_univ_poly + sage: from sage.algebras.fusion_rings.poly_tup_engine import tup_to_univ_poly sage: from sage.rings.polynomial.polydict import ETuple sage: K = CyclotomicField(56) sage: poly_tup = ((ETuple([0,3,0]),K(2)), (ETuple([0,1,0]),K(-1)), (ETuple([0,0,0]),K(-2/3))) @@ -175,7 +175,7 @@ cpdef inline tup_to_univ_poly(tuple eq_tup, univ_poly_ring): """ cdef ETuple exp cdef NumberFieldElement_absolute c - return univ_poly_ring({ exp._data[1] if exp._nonzero else 0 : c for exp, c in eq_tup }) + return univ_poly_ring({exp._data[1] if exp._nonzero else 0: c for exp, c in eq_tup}) cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): """ @@ -189,7 +189,7 @@ cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import resize + sage: from sage.algebras.fusion_rings.poly_tup_engine import resize sage: from sage.rings.polynomial.polydict import ETuple sage: K = CyclotomicField(56) sage: poly_tup = ((ETuple([0,3,0,2]),K(2)), (ETuple([0,1,0,1]),K(-1)), (ETuple([0,0,0,0]),K(-2/3))) @@ -201,7 +201,7 @@ cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): sage: R.inject_variables() Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19 sage: sparse_poly = R(fx0**2 * fx17 + fx3) - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, _tup_to_poly + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup, _tup_to_poly sage: S. = PolynomialRing(K) sage: _tup_to_poly(resize(poly_to_tup(sparse_poly),{0:0,3:1,17:2},3), parent=S) x^2*z + y @@ -238,10 +238,10 @@ cpdef list get_variables_degrees(list eqns, int nvars): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees + sage: from sage.algebras.fusion_rings.poly_tup_engine import get_variables_degrees sage: R. = PolynomialRing(QQ) sage: polys = [x**2 + 1, x*y*z**2 - 4*x*y, x*z**3 - 4/3*y + 1] - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: get_variables_degrees([poly_to_tup(p) for p in polys], 3) [2, 1, 3] """ @@ -252,7 +252,7 @@ cpdef list get_variables_degrees(list eqns, int nvars): max_deg = degrees(eqns[0]) for i in range(1, len(eqns)): max_deg = max_deg.emax(degrees( (eqns[i]) )) - cdef list dense = [0]*len(max_deg) + cdef list dense = [0] * len(max_deg) for i in range(max_deg._nonzero): dense[max_deg._data[2*i]] = max_deg._data[2*i+1] return dense @@ -263,12 +263,12 @@ cpdef list variables(tuple eq_tup): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import variables + sage: from sage.algebras.fusion_rings.poly_tup_engine import variables sage: from sage.rings.polynomial.polydict import ETuple sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) sage: variables(poly_tup) [1] - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: R. = PolynomialRing(QQ) sage: variables(poly_to_tup(x*2*y + y**3 - 4/3*x)) [0, 1] @@ -284,13 +284,13 @@ cpdef constant_coeff(tuple eq_tup, field): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import constant_coeff + sage: from sage.algebras.fusion_rings.poly_tup_engine import constant_coeff sage: from sage.rings.polynomial.polydict import ETuple sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) sage: constant_coeff(poly_tup,QQ) -2/3 sage: R. = PolynomialRing(QQ) - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: constant_coeff(poly_to_tup(x**5 + x*y*z - 9),QQ) -9 """ @@ -306,10 +306,10 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import apply_coeff_map + sage: from sage.algebras.fusion_rings.poly_tup_engine import apply_coeff_map sage: sq = lambda x : x**2 sage: R. = PolynomialRing(ZZ) - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup, _tup_to_poly + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup, _tup_to_poly sage: _tup_to_poly(apply_coeff_map(poly_to_tup(x + 2*y + 3*z), sq), parent=R) x + 4*y + 9*z """ @@ -349,10 +349,10 @@ cdef dict subs_squares(dict eq_dict, KSHandler known_sq): INPUT: - ``eq_dict`` -- a dictionary of ``(ETuple, coeff)`` pairs representing - a polynomial. + a polynomial - ``known_sq`` -- a dictionary of ``(int i, NumberFieldElement a)`` pairs - such that `x_i^2 - a = 0`. + such that `x_i^2 - a = 0` OUTPUT: @@ -447,31 +447,30 @@ cpdef dict compute_known_powers(max_degs, dict val_dict, one): - ``max_deg`` -- an ``ETuple`` indicating the maximal degree of each variable - - ``val_dict`` -- a dictionary of ``(var_idx, poly_tup)`` key-value - pairs + - ``val_dict`` -- a dictionary of ``(var_idx, poly_tup)`` key-value pairs - ``poly_tup`` -- a tuple of ``(ETuple, coeff)`` pairs reperesenting a multivariate polynomial EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import compute_known_powers + sage: from sage.algebras.fusion_rings.poly_tup_engine import compute_known_powers sage: R. = PolynomialRing(QQ) sage: polys = [x**3 + 1, x**2*y + z**3, y**2 - 3*y] - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } - sage: from sage.combinat.root_system.poly_tup_engine import get_variables_degrees + sage: from sage.algebras.fusion_rings.poly_tup_engine import get_variables_degrees sage: max_deg = get_variables_degrees([poly_to_tup(p) for p in polys], 3) sage: compute_known_powers(max_deg, known_val, R.base_ring().one()) {0: [(((0, 0, 0), 1),), - (((0, 0, 0), -1),), - (((0, 0, 0), 1),), - (((0, 0, 0), -1),)], + (((0, 0, 0), -1),), + (((0, 0, 0), 1),), + (((0, 0, 0), -1),)], 2: [(((0, 0, 0), 1),), - (((0, 2, 0), 1),), - (((0, 4, 0), 1),), - (((0, 6, 0), 1),)]} + (((0, 2, 0), 1),), + (((0, 4, 0), 1),), + (((0, 6, 0), 1),)]} """ - assert (max_degs and max(max_degs) <= 100) or True, "Cannot substitute for degree larger than 100" + assert (max_degs and max(max_degs) <= 100) or True, "cannot substitute for degree larger than 100" cdef ETuple max_deg = ETuple(list(max_degs)) max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) cdef dict known_powers @@ -528,8 +527,8 @@ cdef tuple tup_mul(tuple p1, tuple p2): cdef tuple monom_sortkey(ETuple exp): r""" - Produce a sortkey for a monomial exponent w.r.t. degree reversed - lexicographic ordering. + Produce a sortkey for a monomial exponent with respect to degree + reversed lexicographic ordering. """ cdef int deg = exp.unweighted_degree() # for i in range(exp._nonzero): @@ -551,11 +550,11 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): EXAMPLES:: - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey sage: R. = PolynomialRing(QQ, order='deglex') sage: p1 = x*y*z - x**2 + 3/2 sage: p2 = x*y*z - x*y + 1/2 - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) True sage: R. = PolynomialRing(CyclotomicField(20), order='deglex') @@ -567,7 +566,7 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): TESTS:: - sage: from sage.combinat.root_system.poly_tup_engine import poly_tup_sortkey, poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: R. = PolynomialRing(CyclotomicField(20)) sage: p1 = R.random_element() sage: p2 = R.random_element() @@ -591,3 +590,4 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): key.append(-exp._data[2*i]) key.append(exp._data[2*i+1]) return tuple(key) + diff --git a/src/sage/combinat/root_system/shm_managers.pxd b/src/sage/algebras/fusion_rings/shm_managers.pxd similarity index 100% rename from src/sage/combinat/root_system/shm_managers.pxd rename to src/sage/algebras/fusion_rings/shm_managers.pxd diff --git a/src/sage/combinat/root_system/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx similarity index 90% rename from src/sage/combinat/root_system/shm_managers.pyx rename to src/sage/algebras/fusion_rings/shm_managers.pyx index b1f0c450096..416739f04d2 100644 --- a/src/sage/combinat/root_system/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -17,7 +17,7 @@ factory is a cyclotomic field. cimport cython from cysignals.memory cimport sig_malloc cimport numpy as np -from sage.combinat.root_system.poly_tup_engine cimport poly_to_tup, tup_fixes_sq, _flatten_coeffs +from sage.algebras.fusion_rings.poly_tup_engine cimport poly_to_tup, tup_fixes_sq, _flatten_coeffs from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular @@ -75,7 +75,7 @@ cdef class KSHandler: EXAMPLES:: - sage: from sage.combinat.root_system.shm_managers import KSHandler + sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: #Create shared data structure sage: f = FMatrix(FusionRing("A1",2), inject_variables=True) creating variables fx1..fx14 @@ -88,7 +88,7 @@ cdef class KSHandler: sage: ks2 = KSHandler(n,f._field,name=name,use_mp=is_shared_memory_available) sage: if not is_shared_memory_available: ....: ks2 = ks - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] sage: ks.update([poly_to_tup(p) for p in eqns]) sage: for idx, sq in ks.items(): @@ -114,18 +114,18 @@ cdef class KSHandler: try: from multiprocessing import shared_memory except ImportError: - raise ImportError("Failed to import shared_memory module. Requires Python 3.8+") + raise ImportError("failed to import shared_memory module; requires Python 3.8+") if name is None: - self.shm = shared_memory.SharedMemory(create=True,size=n*ks_t.itemsize) + self.shm = shared_memory.SharedMemory(create=True, size=n*ks_t.itemsize) else: self.shm = shared_memory.SharedMemory(name=name) - self.ks_dat = np.ndarray((n,),dtype=ks_t,buffer=self.shm.buf) + self.ks_dat = np.ndarray((n,), dtype=ks_t, buffer=self.shm.buf) else: - self.ks_dat = np.ndarray((n,),dtype=ks_t) + self.ks_dat = np.ndarray((n,), dtype=ks_t) if name is None: - self.ks_dat['known'] = np.zeros((n,1),dtype='bool') - self.ks_dat['nums'] = np.zeros((n,d),dtype='i8') - self.ks_dat['denoms'] = np.ones((n,d),dtype='u8') + self.ks_dat['known'] = np.zeros((n,1), dtype='bool') + self.ks_dat['nums'] = np.zeros((n,d), dtype='i8') + self.ks_dat['denoms'] = np.ones((n,d), dtype='u8') #Populate initializer data for idx, sq in init_data.items(): self.setitem(idx,sq) @@ -139,7 +139,7 @@ cdef class KSHandler: if it exists. """ if not self.ks_dat['known'][idx]: - raise KeyError('Index {} does not correspond to a known square'.format(idx)) + raise KeyError('index {} does not correspond to a known square'.format(idx)) if self.obj_cache[idx] is not None: return self.obj_cache[idx] cdef int d @@ -252,9 +252,9 @@ cdef class KSHandler: num = quo.numerator() denom = quo.denominator() if num > 2**32: - print("Large num encountered in KS",num) + print("WARNING: Large num encountered in KS", num) if denom > 2**32: - print("Large denom encountered in KS",denom) + print("WARNING: Large denom encountered in KS", denom) nums[i] = num denoms[i] = denom @@ -274,7 +274,7 @@ cdef class KSHandler: sage: f = FMatrix(FusionRing("C2",2)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: from sage.combinat.root_system.shm_managers import KSHandler + sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: n = f._poly_ring.ngens() sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available,init_data=f._ks) @@ -354,7 +354,8 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self,n_slots,field,idx_to_sextuple,init_data={},use_mp=0,pids_name=None,name=None,max_terms=20,n_bytes=32): + def __init__(self, n_slots, field,idx_to_sextuple, init_data={}, use_mp=0, + pids_name=None, name=None, max_terms=20, n_bytes=32): """ Return a shared memory backed dict-like structure to manage the ``_fvars`` attribute of an F-matrix factory object. @@ -385,23 +386,25 @@ cdef class FvarsHandler: INPUT: - - ``factory`` -- an F-matrix factory object. + - ``factory`` -- an F-matrix factory object - ``name`` -- the name of a shared memory object - (used by child processes for attaching). - - ``max_terms`` -- maximum number of terms in each entry. Since + (used by child processes for attaching) + - ``max_terms`` -- maximum number of terms in each entry; since we use contiguous C-style memory blocks, the size of the block - must be known in advance. + must be known in advance - ``use_mp`` -- an integer indicating the number of child processes - used for multiprocessing. If running serially, use 0. + used for multiprocessing; if running serially, use 0. + Multiprocessing requires Python 3.8+, since we must import the ``multiprocessing.shared_memory`` module. Attempting to initialize when ``multiprocessing.shared_memory`` is not available results in an ``ImportError``. + - ``pids_name`` -- the name of a ``ShareableList`` contaning the process ``pid``'s for every process in the pool (including the - parent process). + parent process) - ``n_bytes`` -- the number of bytes that should be allocated for - each numerator and each denominator stored by the structure. + each numerator and each denominator stored by the structure .. NOTE:: @@ -416,14 +419,14 @@ cdef class FvarsHandler: .. WARNING:: - The current data structure supports up to 2**16 entries, + The current data structure supports up to `2^16` entries, with each monomial in each entry having at most 254 nonzero terms. On average, each of the ``max_terms`` monomials can have at most 30 terms. EXAMPLES:: - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: #Create shared data structure sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) creating variables fx1..fx8 @@ -441,7 +444,7 @@ cdef class FvarsHandler: sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=name,use_mp=n_proc,pids_name=pids_name) sage: if not is_shared_memory_available: ....: fvars2 = fvars - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) sage: fvars[f2, f1, f2, f2, f0, f0] = rhs sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) @@ -469,16 +472,16 @@ cdef class FvarsHandler: try: from multiprocessing import shared_memory except ImportError: - raise ImportError("Failed to import shared_memory module. Requires Python 3.8+") + raise ImportError("failed to import shared_memory module; requires Python 3.8+") if name is None: - self.shm = shared_memory.SharedMemory(create=True,size=self.ngens*self.fvars_t.itemsize) + self.shm = shared_memory.SharedMemory(create=True, size=self.ngens*self.fvars_t.itemsize) else: self.shm = shared_memory.SharedMemory(name=name) - self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t,buffer=self.shm.buf) + self.fvars = np.ndarray((self.ngens,), dtype=self.fvars_t, buffer=self.shm.buf) self.pid_list = shared_memory.ShareableList(name=pids_name) self.child_id = -1 else: - self.fvars = np.ndarray((self.ngens,),dtype=self.fvars_t) + self.fvars = np.ndarray((self.ngens,), dtype=self.fvars_t) self.child_id = 0 #Populate with initialziation data for sextuple, fvar in init_data.items(): @@ -490,7 +493,7 @@ cdef class FvarsHandler: transformed = list() for exp, c in fvar: if isinstance(c, NumberFieldElement_absolute): - transformed.append((exp,tuple(c._coefficients()))) + transformed.append((exp, tuple(c._coefficients()))) if transformed: fvar = tuple(transformed) self[sextuple] = fvar @@ -499,7 +502,7 @@ cdef class FvarsHandler: @cython.wraparound(False) @cython.boundscheck(False) def __getitem__(self, sextuple): - """ + r""" Retrieve a record from the shared memory data structure by unflattening its representation and constructing relevant Python objects. @@ -509,8 +512,8 @@ cdef class FvarsHandler: EXAMPLES:: - sage: from sage.combinat.root_system.shm_managers import FvarsHandler - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: f = FMatrix(FusionRing("B7", 1), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 @@ -522,7 +525,8 @@ cdef class FvarsHandler: ....: n_proc = 0 ....: pids_name = None sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) - sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10)) + sage: rhs = tuple((exp, tuple(c._coefficients())) + ....: for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f1, f2, f1, f2, f2, f2)] = rhs sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) sage: fvars[f2, f2, f2, f2, f0, f0] = rhs @@ -546,7 +550,7 @@ cdef class FvarsHandler: modified entry, tagged via its ``modified`` property. """ if not sextuple in self.sext_to_idx: - raise KeyError('Invalid sextuple {}'.format(sextuple)) + raise KeyError('invalid sextuple {}'.format(sextuple)) cdef Py_ssize_t idx = self.sext_to_idx[sextuple] #Each process builds its own cache, so each process must know #whether the entry it wants to retrieve has been modified. @@ -614,11 +618,11 @@ cdef class FvarsHandler: EXAMPLES:: - sage: from sage.combinat.root_system.shm_managers import FvarsHandler - sage: from sage.combinat.root_system.poly_tup_engine import poly_to_tup + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) creating variables fx1..fx27 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20, fx21, fx22, fx23, fx24, fx25, fx26 + Defining fx0, ..., fx26 sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: if is_shared_memory_available: ....: n_proc = f.pool._processes @@ -627,7 +631,8 @@ cdef class FvarsHandler: ....: n_proc = 0 ....: pids_name = None sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) - sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10)) + sage: rhs = tuple((exp, tuple(c._coefficients())) + ....: for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f3, f2, f1, f2, f1, f3)] = rhs sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(f._poly_ring.zero())) sage: fvars[f3, f2, f3, f0, f1, f1] = rhs @@ -701,7 +706,7 @@ cdef class FvarsHandler: TESTS:: sage: f = FMatrix(FusionRing("F4",1)) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: if is_shared_memory_available: @@ -735,7 +740,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("G2", 1), inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: shared_fvars = FvarsHandler(5,f._field,f._idx_to_sextuple,init_data=f._fvars) sage: for sextuple, fvar in shared_fvars.items(): ....: if sextuple == (f1, f1, f1, f1, f1, f1): @@ -753,7 +758,7 @@ def make_FvarsHandler(n,field,idx_map,init_data): TESTS:: sage: f = FMatrix(FusionRing("G2",1)) - sage: from sage.combinat.root_system.shm_managers import FvarsHandler + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ sage: if is_shared_memory_available: @@ -769,4 +774,5 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ - return FvarsHandler(n,field,idx_map,init_data=init_data) + return FvarsHandler(n, field, idx_map, init_data=init_data) + diff --git a/src/sage/combinat/root_system/__init__.py b/src/sage/combinat/root_system/__init__.py index 694205392d1..7a14d809bea 100644 --- a/src/sage/combinat/root_system/__init__.py +++ b/src/sage/combinat/root_system/__init__.py @@ -77,8 +77,8 @@ --------------------- - :ref:`sage.combinat.root_system.weyl_characters` -- :ref:`sage.combinat.root_system.fusion_ring` -- :ref:`sage.combinat.root_system.f_matrix` +- :ref:`sage.algebras.fusion_rings.fusion_ring` +- :ref:`sage.algebras.fusion_rings.f_matrix` - :ref:`sage.combinat.root_system.integrable_representations` - :ref:`sage.combinat.root_system.branching_rules` - :ref:`sage.combinat.root_system.hecke_algebra_representation` diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index 7c620b9cac2..7ac32456dfd 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -19,9 +19,6 @@ lazy_import('sage.combinat.root_system.coxeter_group', 'CoxeterGroup') lazy_import('sage.combinat.root_system.weyl_characters', ['WeylCharacterRing', 'WeightRing']) -lazy_import('sage.combinat.root_system.fusion_ring', ['FusionRing']) -lazy_import('sage.combinat.root_system.f_matrix', ['FMatrix']) -lazy_import('sage.combinat.root_system.map_reduce_engine', ['MapReduceEngine']) from .branching_rules import BranchingRule, branching_rule_from_plethysm, branching_rule lazy_import('sage.combinat.root_system.non_symmetric_macdonald_polynomials', 'NonSymmetricMacdonaldPolynomials') From 8408cf2609ef1f1e82f70cf0dbe80ae01a18c77f Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 29 Jun 2021 11:28:03 +1000 Subject: [PATCH 094/414] Fixing corruption with fusiontree.png. --- .../reference/algebras/media/fusiontree.png | Bin 37424 -> 22635 bytes src/sage/algebras/fusion_rings/fusion_ring.py | 2 ++ 2 files changed, 2 insertions(+) diff --git a/src/doc/en/reference/algebras/media/fusiontree.png b/src/doc/en/reference/algebras/media/fusiontree.png index ecad6ab87eae6f8f1870de6963e6cb9db617560f..082e87ab99c5f70f3754ea2acdb560b079c47511 100644 GIT binary patch literal 22635 zcmeHvc{r7A*Y_o&L1ZWr_70&zB4vn8AykGNB@Gl25gE!58>!5*Qo@#$p`<}63EdGT zks%F2NZOQfvuzvSIxky!@Av)wc;4f9zvKOm<9UwbxwH56yVkYNb*{C}^IYq9?JyG~ zy(No87GW5+#6Vxi6vNo*=>H2i;Ty-#7D@0=n6LRhPtzk_#FOr>_KwbWL{FcSc0@aG zM|%wO{&Xt(-I5m@7wAl1$&fr;#uaI%doOW`8rdzi%G#fBQte~qyYVNS&!nTy-#$ZR z+pu2l+aU?>Yi|r6*BtV2b3Jk6=;zG)9EvF?Gz&fl^n{n4^w6se(q4PWXjxP;EyjLr zt;iGY9Za>xkD5~r9g9t{^+})BT?oOjMT;CkseOjK)ofi)Y&de%)y8gv_lc9BErzLU zdY?RE>tyFiw6Qzp=%OLf|Fl$s=y+5^!eX=GM#GcZb`FmEzV3ErzDDM@zD~BPMff~u;j{6BxJD&7(baf#jnMZ6~PkCxcNWgU>OE{W<-_l(?SVjPI z$fGOr3L7@c&xLzB+W%kR=*k~Bx>?P{(c8{>pN``RI~Na7K|?}eqrw(k+Mmmo|J7wA zOU>ZKkz;lijz>Km=G^$_ioTuWF$WmTZ<+WdV|Pb8umc(~T#}GS)|6+N6J|&L_w*ne z{m=jJ-w*khZ2WI?{o7psk_G-H@V~w5-{$(4EbuRZ|LtA>f0=91pADX!3+(E=VEguC z;5iYtejFa#4a^t7|NIsl4aG1bW}vgp-1}4C7oWtYZ~m2o@*KJcPjc-J-J7>CEi?RY zKB36J?$*R%(mR${nhPXJ2S_Q@sBXCzrm^Ldj?-q1=k^L)Tud&CqDm}+oq#r z(!3>LfTAbM*c2S=Ge6j0J<;+xc4@QX<4bJKn_}NTY{@dsF{ciOF!QGw`H$%x5w=sz zaRu*zc3}-)Pi|3_jomd@g`D2s4!g4H*vlg!V#~o=H;pOc`6Z0w1rHxq+nCU z7li)4e;nO^Y^a3Ml)ra6{b}F*T?gG?t={b48bTVmMvWP3@i3l#7qr|%F4*bgL+(4r zh!m$MpVP3sLnCLGJFr=JmKIC=kvH!?By$Pyz#6NHNaU<#UV-1&<$H|&rFEsne6F-HtLwIOS zAE43K{wrzig77Ox!gzx(inTCKEZ_Zs%;^FmfVCnxH(dGp2tj)K-O zQw?9+M%k0A=ZX~PWoq-NX5Oql11_po96uOl>}Qh@WBKs-p5pQDtKxihXQv1L*rW2{ z!QnFG!Xck0eim9AQoP#y$112zw{8_~-L>S?r%xSc+9uNUIFy3^G1fz;Ki+upbALk6 z*8buLFMeK+R?B_ybFhxnHawBKz`dgWYg6cwJgaAFC=Hn1+&$0|JmrL8gk!_BrB0N#`{7W@a@6EfQiB;N*n#qGurSe+7zkW1i8GmE+=JyXcp$NBQ(g!b!o&Io5@z~pxoi|K# ziW$^f7s2le`S~s$d2wM+EBuYVXO0$@=5Y&eOn09e|85c3{U^@yom72?xTXeU0uFw3 z%t~GB<%OJ=1sZqC9yp;^MpOdKQe)p9ij}`?%YU4}B9Okq0{}E(Y_cWy2GY!?@QQ z$n1Ia+2!f$oUF9{mW3{K zC$}GbSysjc|DE&aKT-G%{|SZPFh|y$Kj+B8|4ip*52y%VHaJu*Ke5wP&Fjmlq^)cN zZ8EZ6-oLoc;$`}_ULuj0)Vh^voFzCQe^z;Wzt6XMBk*ydolAm_uukyTR~hTFtk`l1(zl>ivkDP5s>o9QA%x+8Z3nPo0eh zKI_GrQ(9E!vzy)|N$8;q(W{O2vbPB=R9EGcT`q5GvlP=!JdoUW$JNDJHiz^?Bkjhx z*6U10*OmpDBQ`-w;ByvbO9@P0=I#D`2=t9b_l=WOCGW_1$?1B+OataFq zC8{>pxHc;{%cq!A8VRMKjf|5MjakT%AkO*~vv`8Ln}rjdj$(q_fZl-szNH}gP@ z0*KjLrGdnZ$!7voJRigy12HS{LHzj6*yDtLV@*;+~hz_xe(IVef%Q@&)ir4RNr}6$9FuPsG@6ZEJq%67Jq6h_#NIU;tmNtH)r zg6|#698XQ?an;E+env2^_X|u*pqlk>Bi4kdgY54d0h_Mx&NX&bJvA?zuvw6XzL$05 z_A{0m5T2)hS;!ESSl~#ukYZe2)s$5jLvSQ%P3qHr{KO5LIewsK@`!9<4ilq#<|qA;r$m6o2!Vu7knoEo;i21E>rRIRYQwUuomkEFOBu_- z;ez=qQ3S>C1xEV(wJaEWy)C&-gMU)QfcAqrxT=QB0St0z$vk9vxL`E!RJ9|M;rwO| zzgBN{R!^i7VJN+P#?j`b>l9aoZ^L}zyT>ARO0NQp=^fk6UJz|giQ)(wBe`GY zdH=Vn1Zq-@nM7cRsl~;FKaXz~4O}b#w+II(Tc$y>HGzN9DxR9u-z!(cCA%SqJkB}u z{Cr*+`Y92A(@Cc4vU&O;pK?W!LM5J)VK-qS7936x(7f2CkQYYS*eptnQDf>h~)+Rtbj`( zAVw#L?3*%#b?r*MC_P8O3hY#;?!jQpVTmKzLw{r?Xwcfc3k1jmepWz;08=_@M-F*V zwTVrZ_u*cVIX2jVDMbpMS~4$PHFLaX^*dL7fR&QKnTKMjLIF!rYN=;-yPs|A;#<=s zFP^1>eY?@RcCHEwF~d$>D^P*kk?f>Da&>pEb#Z=(5+S`flTm?Ztzf0n%_;4L4(2;) z2WoScU_ToVR{V(u^|*n}Sh`(tkJD0&wyMTeE{q(ZPdgAWy=$1wL8i%dlI6{L-#AJu zB+YW${EQs252B|moRD%mMNuj@mTLKq30@Eif`LF(RuJF0D&&xd`&}JL*j33U*D02a zigu1t;&qVWZDfaKU;K0nMvc2ej12!TTWnEv#NV83CMzAu_w+{=Z_jO`Pgo@LYUO1z zqVTi{1KF3-F64Y}C#|=34L7E7W9m3JYc@K=&P@mg9`%m4ka%wL!U2p!*sIMgE~*^S@v_PHsmdn4q>{t#8(mr(b7t3%Ywi${ox41 z3~LjBwFT7sVN~7Rwh2~hiKtmfey*2mUOJI-AFGq4-c5^}$#0}I{l~Rf)diurCG%w0 zIg&-B81rSC`llvW*Kno(mC5+R8ieK&qXB^xd8U(hRQIr@TQ=G}oFgCxtGXm~5d!UL7GgKP>e>fm z?yGBz6Ld5cOeYQdd0`&%B%0bhSq`P7`rO#FaVIS`88Z<*eji4Q&m;lnkqb+_B$T>j zUe$Re%@h2SIu|vXlDgSVr0k4oH^yUm9m$_k+GR-Y)t9MTSF@#Ozuuo@@cU+FDqFkp zYq~@MB&FEy@7ppckMG}Lfce62JLi-@?pRe*3mcBzJ6uq*jiXNIg}XF9?fc2a}@Gd_wbaTBTZ zIyak^%*w-qI)76ZV#%4I^6o1+P{vj@7}<5x0ogiGaov8F>ea6nk`468e7Mbvvt&14 z{e62zfw4W7-V{ENQHal3(p8<(%>AiUi2Zi|JR_F&PgI}Fk5{?sqOk|QeVFT#fIGY} zY%i21!_FxYFtz4zq37TshPlQPG3RG!NuCn2%vTWWH@;M2y# zNiqzOfF&>RlTxRy8*chz+Wh_!8G~p5*zx8b*7zT+lA(JFe~r>d-cYwGD!1()0@57I zBf4<6F+L{6@HFX1H)0wV_`l#c4X_M)s=e-w!!;mL_q>>KT5iAODV^;ArTrBnR5PQ$ zTwG5>PAhKeYzqD__zp$R?QHFGJzrzohDtk(dsXnf6C7mRK+%WrOjqz7k|@G$Xtp?v zbfEDIu0m7;{x7ItwpfPVsPxIn03UJq0S_(MO5AN06H!u9BNzDxyY+Tuuhw(4D$-WI?5 z0XVd_Jmv~q{>kTis%@>tSR`yCGL4dBVDKmC8UZmHqnFL*VGe^t?_W>8bI;RnslT!7 z*7UrWjX7riSC@Q|ewSrY98AIr$V=%~hx92*6yK9)jq{zEcNmzK2Un06zGG7uaC{Oq z?IWJWi5*^weUYrmvXWvno;N38$(m?CLUJV0x;Q9Bu~e`Xp3KwBpOj#Q<`r?Hh01MkOmH#qbAC+~!-hj?N4muZ}AyiVnLRt~6p_Zz%Y;eC;dV1MTzjH2Ts6!v-eo{z+lXw* z?nu%F+gyUtn2i1wzD6U5gmDIK!*UY3%>?=V7j4Vs)mkw-SpAfCvjrgTHGVGRS`Fdr ztdq+dxDCtPk!<`ufgD|{P8tsrzp2y&sk6&Hd|`&^wsaO zVqs`qDy*s1?dG6t2X$gU|2AcvAv_Jv8xEP{f~h)`?Fn%81-c5R%q&|-IxpSCMWp@K z{#p{qgHypRgJFa7Iut2*w!RyBHkih=gJb546Tg+o-h>9rR_6>?CEv-cfoGY{>Mp4I z9RDJmgtWtls&pCG-9gEbloQ{RAO$z;in4~92yg?qKm3nnl&;|LY-NBa^du-D_^fnM z$c2so->>modclnJ9yv$Slh~ew`2f$Gg{J^~F~F1mzy%|5={*|&9@mqw4B$1ha8XcZ z9l%BAz*A=6;YyT)25`nVCj{=`X8G9Ll=g(xa6b({1_pym68I&XxXOsw7U>!pFMj{~ zVC8gi#h?UM0i*tvvV%B`i3IHw@+l0=C}o_4W+oHDL9gSFA}&3={C%iG0ZG zcQ}Q-0l>xu>3*5xiBUQf(>QRz>REUkz;yv`@!Nm3yYV@V)pR7;fyX4mCr{kSkPPg= zV@$zg?hgL;7!ySpI7CN`66JnEkJ)kfgyzqY1cB5A_u_|=^l~4118Sf=5^0oRS*SJ903YM!AeBUl4 zjlgKrz5n11XYdTb_XA!C;b-Ohg8UH(`*hkLyxa`F74ZIm=K*})ikVO%P%9Ub8eu@` z^bH^#>H27Pvc|y|1Ry6D%?2O0Ull|btOtr99u9NY z=7acD)_TC32g>gU010c2yD3@uAmb`R+|d6suRv5D(7o{%Bs3De6s(d~zqYK3Qxo2=*e zmtC4eGPyLXyEo__q)!V{M5cu*5%}@l`KJEh$ENT^oA@4WHUnDF!Qb-TK|W~bjywqt z@Lb$}=_`?ZOb~PrH_YIj57Xv8JFnp|uY!;itB(A(dyo~x1)L6%t%G$4{GhC<7|ZbW zUpZ#Jm?Ith1f&<7g{4~JUC235)f~kHtlJ?YD$nsR1pfqy12Kn$f&_F|G8kF4&D1|V zd)5hxV5n!oSbVHB#>P%?fNCVbqX1q2@FM`1orN<1{x^cpk-iFt<25M#kyY#f-!ls* z0URu?weAlbSsJ8ktf~nq2Dl`^Eob3mfbRhKraAD9IQ${ZYnl`z7M@j?&6=iF)1zqmP z_Gm8zJEo!Jk22>sF#Aq$$dRLeW+@UJ5~^ATa23qX58nT97S2hEz4u=fy<_``-q0FHsq7 z3z&Y~o$O@Ol)J}mutKO3isc=j+on#}lvbiXhm-Zh{=T~}cofJ?`o5>9^SH2&U?o+E zYJQ07t1CIyWHy_qkIr%d#@oiGua*qZV&?OtgQS zVktJp#}~}SduNKyrAgmrSlXQG)oM98xc9(`WrV4L5RIi(XO&-ZuoKYuVmpZ5V_&>F ztF5QpnKf-p!$|0?y~x*~HJUXMG#NeDH&2qG4`M;9yuKqUJg>0z6MIk>eivJaU~-(ev(lC-h}D_$`-#6tsc^nVU=8n^-b%Ufy}g$*aXpuXyy6 zQ`2Y4H1qpY=H!+)!-cKBy(-{`>caYPbPpZaz*x%17o$F;FM6u+FSqxW$0|Ur<{y=) zM@1g5wti`}^`~Hr=9IS|qk$3){mgaHB(yfy>>jKn#Qb%AM+J1U_e?(dR-IId`uf|a zC!gISC3-E*9Jn*i$JcCi<^=Og+vFj{JAf#li(NQz;zX0?1P~`g(DXS!Y}$K(If>k1 zhG5{=AwUWB8SeJcie2BI%v<1b*7(P>;kR2Ae1^K{E#FCnAKeIi3V!r~&`|G5T5~5v zR79otne`ihV8BE0fWY$-9`m0VjS6DtT0TXJrE05kZQI#)YpdPs#C-)0@1#4)C_sm6 zlr3BXpaJ{Sy+z)-q*(2jq~Ej#th2CqFkh82t4qsK^D;z}`E|Lb%Zup;2Ol|G?!Kb9 zdh~;B3=d)WChoTsKt2xd5aQgF!Li)YMLL2%{P5>vQrOWoM##jseLnUcBI~Px6f-y<; zP7yWt=&y(bBefhxthTwA&+udX+Ok&A>k9@g5Os`jut=F7=;Uf`WH$eiO6J*lM*19{a-KwZXd!JK=z zrlA?Ht`DMb!;pF1pu=zBN;&!W!mCouLra(tSL+{rv|q`~9WRBX!+!UwW&k+_C>r-@ z5Ep};@AIt*5km5JT1nLG>mBR_y4LBj5OV_e*~Avl2168P)Sm*z>i4Q^Eld1-)F($8 znm&VR%%R=FPS`RuOqo`n`azk>sg5~N?Co~j>RHi?vRXJobDuSL-nW7#RU0!fIR^E8 z2%)+#F|U?;)m{gF^({Q|ONP|C2T+1}o|D1**lBZ)0t)ut;%ki+cwBeK8l_m0NCC*A9YWMz~_T5c4R>n*79f z>-YZBHdwy6&t`lY@c&C9gCQU2{OXX`m!_|e`b%Xt*ZaXX$m$b);gKswewmEJO9h)= z2A8H}UMj)bW6qz40^0Rl^I3i`$NgJVh5 z;B6*4Fot8?Vrm7xXCP$&XCY!kku0Q!66#N6lIj47W#Ld=5~iDQ^}f&R1zRH4&q!~x zYXrqC$K0kd`!BT69P`lBM|R2B^j76Mqto(%{fW`eJ5Rck>F*0mn!bLA7+DjCVx;jo zB3Mt`KLqWD} z!nE{CdghEq!65{)$_}8lKtvqceR<=mR`1RzLsB=y7{o>(J5SHh*t5R-)g6$CBV+u! zBrU&K~;?_L;2t57o!yWb6{R2 za1DAV`VC+s)^mph6on3#bZ0AnTGNMs>B&xDY2;3|T(q!Sk(J`UlXm)sX&6d(X#rE? z)LIQ#=?0?QWLeK^kQp7Tn}wiAUFY;;gp z9ylZXyDqI?qx*IOXakkt0?UjwCq0XYUa!N|yR2Z_0gG28BE+Dq=vq2?T1RJhii%r= zfu>+Zfn!6i{QH_Tjn6+c>TZyK&3oMg^rD`Pza%9%c|<>a9o^h~PykqD{Wd*y*P4`F zeSyofW#UW;5O~~PugNM;DjB)4zS6&ZB8LnM=;o#|4cI~3Ob`%}~*(IUPEZ8Ut@x{hA!Y2TDn?=9Wl=?d)$m;$4I z>3d93akJ@r+!^im^}LY1e;XcFXN9c&6d)s5H&nh&QGM`YF`Iz&?t#PC>N3Y+*Y{=P z>AnviV40DW=qP9~ihyjtM{oM<{KXxilF^}Yl6o&8Q3?aMCgPK=IGyinfVBktRCjozcDDMDbjbJ-5GKObxl=5-nD449s1vuKl2 zlITeBcst0`C71@85fZ@#NFNG_*oSCLh|7k!fhv%ZI)O12g`U4qCS~b^W3|!bNro<3 z`NP-U3H7MeP__z~q^STW$QDQ&3b3GjfXTv#06dSEU%cAHp8kAvV9hVhev~8D-9Gem z3$m~`?Aatcp25pKuswW4R~54IKEecF0xkp)b}EuenRdL(7a1n=j$Bawc)bzG8ZEGG zK!bSM4`UEk_dbcDg2Z?ibDA-@ZtrchK2`vWEyI{XQgHro7vC}g=gf9Zf9Zcg@-VN< z*mifU^^67Awmb4glfVm#f(@+~{Ry(y;KO|XyoQ*7f#+QK@K5!=*9F#M=hQ@h?Wl%n zm9}M@l)%P;pm_s#AWJDVv^??Xge5SqReX!{K16j%EvaHaq?UgTA&A3j7Fpsy@$zA_ z)k?E0BU<3Kx?(3%-2s08^vX;|ov$6k>I>L+2eN7Y6g9sCA|eK&|8av`>G7sq`kP}U z0VD(LUIBsV*kXp9Q9Pxc{h4%A$onN{6*I?Is(ni|v-#EX#BF+Vtg`N!w(>ab{9>V( ziEGR{AfO_61L!Z@zMN{V0EK=%-+m2WU}U*-NWvU7_kC$pVNJ4M3>{<40CyRh*=4 zRqU!kSb)oa7Pg5aVi+4cXSqcwqe%rAjkZ~-r-u$-8^HZm@O9*hO&NK4azKhPru?GR zvInPMbAd>E3=}L|AW7Y`sR&221u2RUHrQENSso@}3mY~Xan(Y*^=`mg(uaZt)DfZ3 zo!(mfO58+wWi+=8z3%b0jcy&~p`pTxNA(Yq$G`m&)HlDi!j=1ecD&N+ywQipuHYvc?+Q42(krM^MLFdP`-ig zBFGNc%HS;ku8rbsfkN=SfM5EzOh)3VKbD~b=>@MT$9J>qO1$G*q@8Pg1&s_2;#}b` z7m`ebP5oah{?Xm}f;k}=(?1V<8Hq5Gi3$*kPRIIM^Y^49xDobpIP1YFADe( zp_velml>hJ0;W_7!{M;$iB$yndVq_thN_BaRO&OES_q+ z6#EHwT%kV_)CX*rn%VfURC&&IAILvhe;iUvI+Fq98Y^(f-C%Y{Al1B`g?|M2PH>{U zIq;h}TpZw$0RN#+`&d5XC$owJSu6xDbl%x+)}99Wg2bqML(G8wel5@h;4m|Tee(i!5BNe6nL z!vIZg*#t~(6qwUtV-C50cD5XNV78V>H}&K01fB+YSZg_&twivhbRQ`OW+>6r-#Y6I zHBeegH^^i>M9CJ_TVc3I4RT$VYF&ZZN(N`BJ8C*9H=ByP<{SiH&OrF2O8Ug5dwaJXJvQcWCYyHFtxuff!zavrpFep zm%(eYQT4av%OF{B_Z^%qa;=;Vw8-8DpYX;yyI>L+Jspk-z(j$rj@CU0R@v->s3*@- zqmKOvG9mCscuoAk1J3SJMeTEO7=t7Di^<6x)Yz{I;pc)yJDtaLR%xObfORqxfgb>b zE5E=3n0$SmwR6;P2sAhK$GhuX`Y}2LqJl2YjzDT8gU(Vnv*&`(X53_{5xW5#D&(k$v*^Y>!sQjVgtj<25bL`{2GX$0yow5BF(IlehlEBbc9PE){ktv5@ zUqTe9!L7r)ng>=dEw;_;z+r(9;}^(;8CPtmnw^y{XclYPi(EtlChQc2R1wIbw00Kk z2f0FVPwj6oG@-O^QvU5pUtZoWDTP7H?menV5jX$!bpV=H~+v03WvI+)-f3T><2Vm z(7`r=el)DA8N6Cq@If6uJStyE!eEuq9)#yZJoIeIAqhqR!RHRl=m+RS6r{n2{ot^N zBhYpqZUNf7uVF549^#$t<@d*qOEiUaL(-}f1Ff=Q(~%5MM-N2%+9xn8o)B1pF0iZY zgoUMNA!H`hlN4d0UI6MIphgk_B@U=bgjxcq5!jJXS*Qj;!IFxNTmlrdD7E_mmCc1v z;jq1Bp{fDp1*padK*4IGJ%&&+fI?J{A{Gkvc-PJ#)K!Fnu{Xm9rLzJ+#Diw$As7l| zKND|L{||~_(9=8?ax6&Wsix z%-`$>G&4V7W10##XTd}i)sRe*R6CWu`ZM#3zC zPQL<7ql)Zw3GUv32MiV#M}43G-#>vcj@_I?;)8o%KVJwpBv1w5NQ4aDWk5rgTn?55 z_Jg$%f}j|_0eeRoxEGx1=#E@#Ao*gicYvQj3%459E3Flo-V{R8uW5vB8(HojYbIL81MI+T%}#$x3VB zr4~K{y^L$icnXvz@Q#NqVPYS%@0g|&qsrCEDPrbr6GoUNyz$Zp@2v0*S|#^?mP2m} zrSmLbzi+9Y*eUbkDMoMV6yuc)MbzTZsXZemHy)44tOgQwGJy$iw?agSqy!Q!M_*Fm)9t_iJ~H7Us3NJx28Q76;N#< zK*1!clpawU3hzd3Eg7$IeHkW`_Tj$fQ?DlD7^qwYMuYVmE1*+?mzg5PHPn7V-OZ4n zc^XQ(5pB$FW%RlsYNH%3(yiGva_&!%f3mX|1(*9TFw8#s+BT zIH{d?C5)kV1yr9TA3;fR`=mzkE%H5|WxkuiE9jIkx^Wp9h zx8A(Mm(VS;?$OK#j5xvoQ-v%2Yj-V$fL<`F_5bBDc%3kry|n+5^|~Cg0t8sP6Rb84 z+0)G0>BxJwl`m0Iln!rM#y38W0dhMW@X`9AB7H{+!i0el|u${3s6NN zghVLcZqx?qK_d~hJ=4@xb4m-%OBGnYJP#U9(acp;^Vsq zFN-=rV(85;Evatn9BG&?eKxT;uV|`MWr}n)@7<20)Seg<)2oWB5@ns&_hu|#Cis_- zu0T;;M!HK;sEdTGMDDEtn~{*vSwuH41Vg$8vQPS zt7|coZ#_z;uZ4(*E4O+}imY6@X}sSxSF?2YFq>98du(7~8+ogE0CP}STs(SobhKe= z_q~0QQ7e^SqW0_{t$M@L^=ba2A3qd0nswJcHxD3RhU{F%MO?M@bYCbWVNDk(U2mG| zD=X9TgC6R;PoG3?S>#7Uve8IRPIhC5$i91ziub_b zagQ-x^?|_JFYINiito4reU9c@i#7ORmjhe|*ba+dp;}bVJTo&nXX?roaP1S9d)T#> zT&7aez;hd;SNdVuY-W=Un;`ANFBe>~$(?QklcYb|J;IiLVK;612;_%{-#LN8U1dH^ z08PQ<#D?88LjYMJ5YKX|c^f>0TDb%SKHr{7y^32V@StU=nLp@~Eid4YWnVw9*rW_O znBfu3i=8SkxyfM3YF_>~et-Ot3|?$uRqxD)PIw}JY;#TtXt}@-+h;PQg5Q4h3aEc} zXhv4oljmH49N+A6T|vot1Z;VMI<6Q|3=|{IzpsYhGIA20xF)t~R@UK5>y425#6t+B zdw)jOh#Nx1Uz|m?2UN}IRsTwdfSJnX;bYO4SB9zB?l?P0Y>L-01r1oM2q_kRV@8)B z$Y7@Hrr?7TMSL&)DKK8s&>_C;Y-kq?n>}$?r#U z)Kub+972k@w>xK{X^AC(_Yk?>o}Lw^ANSx2KFSj0J<;Y{al1|Y{`fa|zPso+FNuCwM`vVgOwYo?;`Y9K5)xBax-EvUj*TrW0t&r0e$RRWSAoORSx_mXHXZ2LinrD_z!k~_h2Z@{=;=}JPd>E0RR1S zea=mPuK%wTI-?Xmyg!=GjO3rg$FKi$`1nZwIsE?>LUE=3SK9o)rkv$Lb^{hRU0=xCl04ouY^)&$ji z(Bu^s6N`fFPsRJZ1DzLEx@6O*fWg-lixDdq!A~`G31h?nai6X@@1GBwB_uNL-BWm3 zS=kAqJU3#oW+;0Rw??Q{_vjqFLd!0W05;l5bi?H=r1T6bqm0|&hT zN;Kf5|EDTGLq}0!`~@c*)E*TR#kvB~dJ(5_X0#mk*lXua=a)`}Bg)7nd24aR1rt?u zm*OFiMB%lbg9U|!dakankufn_)v+oSw1!AN_zsM+>LmFXr~GGIZr;4v@q$+~681L_ zna?O>#u{H=-;B~y%@QD!35$wGuHJP?MF*>r#czG;)xV+C#nCbBO#d@wH8nLGXmKwC zA{m^MEXsGuKZ6}6Y*+?+uE(fpVO4io9$Zxd2a56vA?t$*@A`lI2m>0N%3EL&HI!uS zTaWV*x1NSy(7$ zH6@q>EfP|Wqu=`%pWhBd!`1r_9H^z2fEoo%-{w1qc=&O5%KS(vwSHi4czgfD+}trl zk^9yh!Xvh}mWHn~jUS=I+l)Jx;PJw)ChxM&&W6&KRM zL{D2=`&;v^y?NoVecMATf!{sK?eO_gf8$+aW0w}%dSbLKs;ZWQ;}geS`z0limku8M zSwKMI%hV^}cS~%2eT$gIuqDrJRo2iDgBV$6{S*$uVzOvwL|n+lD}LL%1divoeB16E zdTQh6SIjJi-$sI??N<05BOLQ!djHc)moD}6_1&iW_m@tSD?GqC(9a3+2;#On_v+QF zpYuiv;)v27ko?D37Tfrf9a;TMzOP^kd_`(8T*YdWH(?$FUOGc$Ak{3wsw zR>X@&nEo;ie(1@;#U+7xJy8R#16 K+}VEk?EeAhwv4g> literal 37424 zcmeGFc|6r!_dkxmS1Azyo z&dC%E$a45|)`59ll=n;&CeBx%Im>iyzdbgC3c6o3jkj;=Y^@d5S7Y|ws~kX=Iz$32XXKGioM=hf6s0k`*78@77~?++TkaSV^wJ8x{}mE99|xtXjFsWtQ5 zaZq@$;po)hPqr<0Tsn4q@tWeqbLBCkg;SSK z+UWUm#Q2wK!-Aa*E?+kEKK7++lH+R!*PpXWQo{l`P zVzIyT;uF)hw0;c!d2_{i{`hje(B6XIdrJbsJ59YQNcldoC};okDGGn(WgPtQYnis7 zCA!XU#a8jX4k)Vi4;bA7>fq|Wh~OycfVyb+A3dJ&3JIH9va#1ZNtT3 zJ5K8JhJEG@w%I>@QD^0YA)k1s|M6+lkUsnVWoTZn#-jwJ~^dVEuUq>%Hou4p+C{b&OOu zwEY;(I6hJ2{^gCZGWxc?i_V;#xVpnXK+x#Ov_)S3Vuu<8U9xV?%z&3K02M= zkhApis$=sfeU7PXE@}^|NM3)fQ2+hbl6~bFian3kx`9dMSAN`SJ`=afZ}@wk=ho%J z%p2#XeG1byX&To>XRBk{*W>;d1}Qw+#>oFQ_TTe3bn5SS-sL|tdlr?c2ITpFh~AJ^ zer8)yM$Do&+rA!q-uN{kVbQ$u3n@?LU4Q5GFx)()y{)&wl zYg>=jW{t926;Pe!Q?PLM>DfBlgeP;LB=72~BZ31ie0_uYJpbqHans7*Jtvj+O3>uE z4)zBn7Prlhb%~g|#p1~N_SWAEe2W5omtS#z`Z~||)kz>v<-WcSa zT{7&L&!RWLHPZY?^a8U2*f?GgBHd{Jq!1 zQR8&py2h=1cIoE#isu~~t{7kYrR9ReK>{`M`JVcV_x~eWvi+kUNT>%aQ|9fW9WeP z<|5su2D9dG(OcfEIlKF~|Lft4=C#!r|5pe>cBAc%Z!O+^;^Os~MW(-Ay9^6zbJ|#p zWpZH4JI~n(9qU9V#EdM3Xi?$s74r@8P*6=U2CWIJL1@d*iJGZFOhOA8&0z*V_Lx^ib$C)_zQn zi62F~k2gmIx46LIRfXpqJKCv(XcGB3k2u3T@HRi^_nUQ>VFQch3=SI7sY91Cj_R(w zW*Ph9{IZ^b0SSS>4mG6iyuDku_`-xfoBZ7kRFlKh;netlpZzpzHQD5&{y+Fix}V!Y$vZ3Zsi?V>xS zuKMD&H|J`+FRi$4v@)>%#WN$QsowRz?ajg#^weLY(DXk6Z-tu6U8dLFC&dMUlFDQ3~Ugw}6vfoXDMh?#bw z1sk&{+cK8satyjWPAB+Rq1(u*Mg5l9A27Q0^WKS~w@^N}*3=anZ;!udpE`DwokaMu zYT*A&x%Eh`vUkvs^j}vmKi>Kcu1dRk+ofAI53YC=O0I1BbnnDhE6Z5JL-lT>(_3ml z2icM3H#VJEpz%MdKm7EgIKwWw0h>&l%1%!3`-atGNj^lH=b5QG;S`yiiw@~#E4^~{ z%EQM!iLZ83AH8ZyotuMS`RRWw-mN)%?8~2>IY-EviI{K8T9(;+&hq==Uo^SC9_HxT z<)HfW2ZDaxQyy)V(ERS}v$V~X&tUiCo=hwWuRA?Da822f@sD~2{R|;-`IH`*eI_EP z?xfk0|5+C4E;gcdmv3)LDqMX;b&$r{OnHB#)na9a@}E z{z&k7HuulGgNw8Vct095?s&-G*R55}S?k-)iemtBtWIR1#aEDnVg>svtcl$tlLyo!9 zFqk)s(LwXp{5<~qmfOk8*FX4G-p$f*n6ef2#Vzm>{O+1f*gy18H?p5t?|lUR+41S$ zv;J&vY^lxrzm_MlGl;fX-sOAlumr1zaibT2bhoCj=Iw{tT7LweU4Q+(NmWXoS^17W z^XJUg4fOH*eY0rlGw-QCGZfb})_g8Mu~TPW#MEE5Z?GDk-D&=QIB2h-s&odU3$Ux(=4Xzw@*8D ze+?F9M4fi2e06z&Lrby7^Cinxe$HYfHCThEPVP4yF||fIWR(gNpY`VIy64@79gPfv z=(mj#UAO>y6NruQ{Jbf(;TnoOagJ{Al!4fflsmLH#c7;$i8UP9{4^pc2D{5kJ}>Ru zPQzXJPZN0@4g6JDe#q_k+_T5FEN}V|++NvPK{V)2?Xe%HpjyVGR$Mc@-BfXYRWF`$ z$Xy=&cyamF5%mA6N{XYq3wT89*@Pg!U^qa+^0WMOdEyaCG|7JI(KZoQe6+ zw6EIrVeGbrB13!c$K%T%jJ#|${Im~tU#CIat5toO_%DTS3D)Lq)e(Qr+t`}jaQ#mG zsL|acLoJO`g%$U{V_`4A4q&H7XI`YT(GAD7*BoH*%c0n{fBU%K&A2?yr$X7GVfykr z0gVN(maW?6+SKyZ{70yQX)W;$W^r9b+J@XHy){{T`mKTM|5?mrC@F{u(~iW=-ShE&*9oiK2_gR@;%<#G@+^B>@7Ys%_F{>IM?mjdfyd_F?d=g=3vCkui^AyQMhhhUi)9 z3o}JVxjPgxg!0CH{XVf1Qj;D@!xYv>cT*Jkp|LWuo8m5OeZycQyRkrc#ads5sTd?{ z@JtRRjkT2sElgD0qd`%DrK}e2UWhQ{p6e zF1*Kx`^y{7(cMPw)w4VUI`l!1AVuB)K}*#^P;QvQW+G?{2(p$DbYThz8m6sOKm@%3 zLDXOgg7=(MVUlCN1g5^amqMF1Zm3~M9>jE z%R!)Ht`r1amp6zYlhmYppra%T1WiF>2|CbsvpB=h1FDMIdVQ3bZIQA@uOn8rDzUQf zgXlYTK!g4622IS?Q1*Qmeb>i#G}!N@ z_qu}-R>LrkEAWOLkV#Aj^hB#FBDkk08Vfp~gR`JhJzCH56Q-j|jJA_E*zfkX;DZQk zilvaJ&lsvqg?n=X^wNq~u ziBtXp=w5Q}j;yaydYXm&GF`BK*EGOkYD9TZlCT?er-^3dd_XlMNJjYu-O_u1MX81~ zKq5SSz{w@ml&NQFBXR1L021$mG1amOy&N5DI4w798kCOdb;Lsxb7A?LE)DGrzEl%2 z=Ag<-MofK#W$Z^`CQpa-pi3ct7e|al*bRE)3>rj1AjTHhp$3)1Qj?BHs)p1MyUc+` zMLtZNW$gcaZ9` z4(qU#L>0eR)&TGMCPOt|dP|%lNhXec(HJHZZTu4u1bjh+cp(r_1BhKTF|TOVkXuq9 zE&)TR0aj=8y_`ldTWUTF=~j7Dj1hgA4iYhoA~J z5TZMDf);uBy{OQA9yhD5LHPz_X5#s17|CX)8EUUN1Bhw~kvxWQI}rBn;F_1DRl;aq@xejqKv1Jv0jzfYB=2 zKMiClYtTUPJM1-Gpj@;fm`PynS~*3FFf|B0oG6lRmoV#D1D5kr)@#&U!W@uQ#&Zr=qr!)l=nrc4q>&qr!aHZ92sBI9CN3i z#E%x3YFVE`{@);y*1M53^lA4({$N7hG{+>|K#qK*k)3;}ho%}48#q+k;tx4mJxlfF zprV}QWEQP@k|ReimA@S1?8bLSm}w9vqs9l+T*Q=!N=D)oLrwxh@&YmE(Q6<0n=x6| zpp{Z8Fbbb94O`d={1w4{7=+4(DZQtsd@NG+BoV=k%Vj%|)<}1=--r0p_mpT=wgI&~ z&?l8|3TpNfHD88lTU?UOYRCj5yE0g}Nu*8j39<%pVSt?^e|RC^i+n5JRMsGYPl{3= zqAUSj;F@Cq8q?(y_#72#C=ENHNQ74BvJIA?Lwd?~Ae1&y%nQhPyv&wJ57}$l9Z)@~ zOy8rywvlKMp_Ahbg|kc`1+dv_zN|s&-ObQWy2nG4wpC1Ggsee?_Lhbo&@1G>;ek*a zb2J9aH1qVP-088xO!p9QRM0AG5JzL~gHX^x+bYo)Dcc}I4R(Vd(9w+udW^z9 zS%b!(iS>T~KcX8W2tq?X&dg81n;SNU9lr&5(IC9Lz##fgeoYi0Yp~yE8ioIjAH!*! zHeqHwbd?>yBr5d3-!-wINTml$geTl3PVBDArbi*4wo$~NU8VQBPBRIQAx)e&R6F~v zToYlkckr~clYXy+YVm?4PPba`JcMb}>ti8|rC)^+;#6TKnJSDU#W=G}E+o)64~A*K zbdbqQ?U<$d^aHAu@Wza&CO52JzI(01t=$P}SbYSIGC<u^IjXlP+l5Sf8pvrR7O%E&yqz|gMiToXex8vcebvsgYWvjfUQj0=DqH#1aS2OuIpX?=qDAk9g-b@J^AdJ+KKiN_p za-Bph6OSlNlJAz3QlgbZRHb2?XV7%#=7w#S?OyruM=aG}i#%*6uyn@A8rZglG}=ir zl!Te=E=sUc)?n#eV5AZjsk&|_A>JIVx(>o*5yN2ccX-&+=7>o=B5TkA zK^)Rdu-6P1m|V~y6L!Vg&iWYiC{djnt*R}jIx#^{*-BvIp-;%>4bnD| z>%EP6DnAj(RuD3SeIBpn+u4xSH-y`@q+zPI^{01L2=-1qA#7YIyflO^)X z4Z{MPi8(z!pd6xAKVBPCW(4I;M^)t{9R^8j3toA=wsSj1r&__&ro$b-Lq5Uy3Kx@a zhu2-2i`%Ui1+R#$F>B*iZkdaFunz^V0;Pn282K2CRI1a{HZkn;3sK;+xXk&GjueHy z|12ov%Y+~vXk;f=2+R#reYnFeiD7IccZ8Xk8mZndTZuriDUwh#ymN34+;?f{w2;47 z(~rz9BQKg3$=N;5Qr+FAz$2bG`E=G}j*2Sii_)w|^dpn6p_bw#*RST%d*aRuCG;ax zXMKv`XeH#U#u?`Sg#!RFzDa)`H+&g*c!f7z#>6{&Sh7eR(}rj$jT#~6V$s14E~bHt z%NMVe_aDi2pcUwoN8g><@63lqY=g)zJ8rM(BaV@g?{T4np2#ksXZs=^uLEQHKCNzy?~RJzxcsA(S;pZ5ko4aGwLL+(mw}21_VRPXDzdWrWtR z9Y+l!H0G|JvM}+YjB^F&*#=3{rUwYM(UuYFZoxL#^*R6qC7+QIlz&0LgCIK)lzmu6 zkRV0YAc8(8gP`$pg0>j54I=0PMBh14PEggke+kOfQ|>FiBqQj`dA7k4WMrvsAQ&hk zsLXlYf7SGpBrUcwlkxmaz0O`4`y^Z2tfloX8Qf{?Zu-In}i>OXVBn zC(6G+oXCyCQ;IXR5igXD6B#FKkg5#8IAZw{h&(H6u-`#XQlX4Nk=Ik%27NaKJ&q}^ z?8@V9L}S&q&;3Aa2BfcpxxG&Ec#SYOd0(cpW~}PuwuTbddrT)R=B=goc%t&cr0cW{ z1s~41WtzZ;_S$QudA~HObEo*@s{Rlq!<4S~Gx5mG0aS5hw~!l#b}ST97(84bCIiaP<@mZ$mrqXFz=uX%lbaOL+0{{^kCziGiscTDq$ zHS8tbJNiz%SI zMjWB11sK`!c!jVdQhA^vzb9yj^q_e{f7oT;6XVP=QDK-GxjNVFv{dhE#eR`*uY*(U!m95ADE z2_{7j^Fd!jX)1zsIF;a#MvG-Dwkl;~2zjEM85v=)U^(nStK>v$PfMso598Z^95{N%=V#UKU70uvqY#cg3$BiTPRUlZm7aR8o8JJTqSr968o@y zaQbJts1Gi(U&I_PYv9snN2d4#T)iUVI-L`7Tfs1Ta&>W!(o-hejm?i-K>5b0e6a~} zC`LXp@fu9*4WYN;keLA4RTe!36Nu>bK(-bEVMg#>j_eJPxrY-n3Lp^A+_6;hH3=Do z4#=nEn*iAuLRQKlL#S1hO~@hh**>)vC(s;(i$CC23WDXKoxj33HJYR=l1YCt6u?Il{S&;H{kKv@am~7!kc* zex{M)Y?zNA2g4keD^;X2h~7*@8_TT=PXoxFu*m+9BRdMBkFm%$$dRRjXj2y1dpR;M z7?ZN2glsd1>;RBWB4qmVAX?-|AoC_^HUIAyOLg%?8io$(G;lA-%Vq$L(;#Q_-;v6Sdx-ILw?XDu&==%6 z6M6T^IT8>;V>X5aw3P#bwD#bl2{@far`(JD8a;Kz$d0$Kn7v z*B7P=74DH74CH1@fOv&KlL-DSpVTfKsJhM|peb^orvya0V<11T3Ur}OpGf4TIMOlPOrBIkPDK1+2TJBKOU7tE^H zacG6$x11GC^FSawD}OnGf!K7HsnX0ha8Qs{KJNDzrb=$H%O*o{ClEb!I%X;K1%)4I zUhX&-lOPxn*ppalET5L>x-?YbHpwb~ifkbRC|52n{!x3)d{zge)K2rzkBP5HPKV8rssyJF%_d(Dh^$U{Uyc{2239V0tt1j*xX`C6vKJJXMbR+A&M z84YB62x=Ec5Kfk&Zq$dfP&Ve=7d5$_<*NXS?ep&!hzzF&TzJS$roh|Kj#*3lkQjM5 zU$h$LumBe$DD{wQ-ea%aPB!Cc8hzzFGzJgFtBD`o_}0~t?4j_e8{b0TCt1`d1Otr0m!Zpva-MAGkZ_SXlEkkw1dvVVpl?@Ek`B|0kVgL%tVfi&cDn+ zLUx!#hLbO2O2~{kWVf(7Tg?4^bzEHobvCseQj6=ld-=QZ^TPj9JLta7D%{h4(W?;) z_qf~t-EH3_tF`L}XD^NLvs$r#`K0x(##cw|F5hkW?&`uG7ZZmcd^1yJkwVI1>jM4h z(+X}d<61*Dx!&*%OKom$`;hlFz4=J0@5CRE7RG-yE4pq?gsz5e&4H;FTzf54aAzgA z5VLs(vl}@z%#c}6I~`+U!W(>+ARs7aMQ6dmnG^~Yq+N+GOCf|cSd~(v8S0{Nvd(L9 z`x!|q(gQmJStoU_lgSZ7razek6$*~CgXcwd=D842C%D1Qz!@QJcKD=D?BHDpjy#CU zOe}ZWax^+gJ4NF7#|ZSeP8v|>a9SuMV4~US-NE#v6G5iqK+tmqtEDtzkg|Z?5~%^~ zA0m7hfzg&|Fy^`lRxombi5=sGtN~^ZokCb_uE5p@*lesgY-G5OV!k40EK3^11v1Ff` zWu(G83oh}j<@o39wN@7$1=%7WZfKI+B-%Ev;W&A?^d94dQ~~B$2Qtt&X9sv&+JTIw zMxvA=k}pGM4P5Cy?HIh3etDqlpK2+>P9GDZz5(j7TF%wY;^(_}BWNITisV6yoGaH=B`i2JkfjS4Y}5gY+7Uk}=5!9B!u=NAj-3$_yL8<>O+FQ6WD~ z3aHr~aEs|ItjJ2=N|TuZH4)6B!I8 zi$cb7u9Lc#6Qe>W%a`(n!(2_u?UWG-j@t;iPD`O)D!5!{X2CbGgjWpGP?B|8%yrTM zx>Dp<^T*zAJ|bj_@tz@NFS{{}5cq z{CW^vH^9B2J9e7i6`J2D!gsR+j_@J!%eZTB_~sMbaKJ6^fTQ`1B7EniJL-QUjr0EP z-ygJ<)eU!{R^)z<$=`aR1Y^!oq$GyT7f?cAWamu#PR#N(D@{CF8miT*gG|05Y17*i zj60Nc73tW=ZhSes&iT!iHK%?3E_=V<{3;7IU>dJh`L92^`K6s|qaU(atsWhnG!DfQ zeRBqQTLo0DbJ)9oe-HCWK_-iUs6?g)b9k=4$3>)m>g}IWBSk9VT*6d+Hn{0*^`X1~ zeUqYq+IW-o%hTI6e%ICAj~V`FMU!8OTi*UY^Cu2kdU5#@)0?P;Dn|Lu9^HA6*gbG? zhC1^Gwn()4K(`f@6Ve;4eKv2-D$d$lLb;~)C~Zjo9J?actnKBBjaf($@3-#dh26V$ zsSUBXAKd;sI0w1cOKV@X6r45dSNr90rSBFbVc&mrXk4pfCsiCd1+hM9e=t`v(>z|A zRzi`H)RVLeDd+bas!LpuXgX$FO=e$1L@r=FFdEY1A;t=jn-N}|KJf8JB} ztj^Mi(}~a;KjMt_k)<~(`GHM!@e?+#8i4AIr_Y~{`#5zWRc!=CylOw~dX;K+Y;XH* zE^NHvS<(W+Q;>st{FP%DdYHM$&_wV1I-hrIblb0`# zelq#Tt!II@rlzLF$gDMd7iha`>!(lx|2rjR+=g4#*{?lP9*+q6fcgM+L_A0Oe-8_Z zJKbLs(4SjOHoUv>di9a@G$bs7Ks~j3 zYKBE!eSOxuRPPcLy6nx`tC4g5@*S`90LUm*R^Gh%8*;e}{i>g{aeqFVt(6t&hWGO|3>MK0d(p#%G zc6qc{RfxR)WQ2h%Hg4RQUW+Vf-kAjn-@Eipe2WCXVv%AGgZ0Vb{m1El$xGO{x3lP) z_Lttw;$KK$&hkxfe$Os5BtZ8w|C-m{l2?L~q`f0A-}1`un@v+k2|@%?xYKHa!*{Cg zr95*gNolx0%xs~w^8NGYeW;jVc)yqL-w#~5a%JlxXU1l?)$8UBsMN7V)_1@+7hP?i zjNWRkAKoQBd7bQXUMFeh%$A=!%?-8D7~pwdEl99Ejn=EHmlM+uMH&;c_i1ZDr2DDP zraCn*(C8Ao=Jyj9o_t_-`TF$G@y!bnQ8O+^rrJSN~0|2Mfm&q6`yx# zT?+*_@x(P|wwrl#q2$#zRN)mP1Ap(vz{(YB$y?65q6j1z2-Ituh)UH%Nxw(gCh>?p zM?55RTPl{N?-_{X_l&&T4aIv)&Yizh;rkLxBfaJGXcvroscD_=){ooA1h=(FQgl&@ z5;SLf8j-#kmhojRsz2|eGExiGty=;rR}}3*t*5wHq~}~Uda$3LU(@?X^)(MiN2Gdx zwU~K7q^FtY*M#68qPhCDOT>}^NiDVJ?cM``;53<`iWldb*ZcIfUa(+6`b=A2U*F+_ zl$c87w*GiJTX)~CT_q9bLHP=n4h{}fapwAU_Q1`HtrcU*J1hvQiI#Z(!xvLvzI6K_ z<%}OwQ1VEyoR-N#! z7uDGodZWs%a3YFROWykyjcR@ktE}7{i0zhRqVQf*^T#gcnzS+lP^YU~2kU!8WAvQ{ z=fXzZKQDRyDiAUp@gd?f)%?xqUVuzM(Gy zXH$kktByGliz>t{iR;7Sp6x$-x;#5Iw_k$qizUmlz?P9g9;lxfQTMy#RP_7`-}m3s z+uLrSinvwW$%cwI?!#!<`%PI}kVszW#O2($J9VeGqW%f$ye>ZviKcHKJPGTmt3y@A z)()>)ZAGiei`l~(oX&B{4&OcU(v5qe3OsKZy}`vTwHLR1>c`~*I!f-Yl4wgJbw_du6n+1 zsg{JqocvVf;s6g_B zsqWcuYx}h&WNId2=_;Re5+1=t3PZuN)JoAnFjH94?qE^w8XP5WGXGCHVw@1C#2urCqk zO-31q%iX^^d;YlnJDc22mjRmu#08W0K7SU)*Vo}c{;$?Uwc+gREq5!}sEl>0--h1B zFxA5+CqO@Zw!Hk+c5~i8N!BKX_a{Huewdcq>@Y*Y2`a=@Q}cW<{OtW7tsj28sMBh% zcW7U-#l)|!OmqC&0*eGH!7-*nvZ7tnE<6m|r)zFD_(NV8v^dbH2aYv6UbwxyV3!=w zSR<+3+#Yc;OYqyJF@L97>0gRVu-E5BAo@3b#qYg`bEAj7s%ybcX%y8(OKM@XA7rhk zt3lztiexrJ_w}d}+=FuHYhN6c7Qb=cy(#6@ z>az0k^0=;*-@jklTC$3HHl)we^ba3CG{glr#z|_irp>}s&yU)~&u_JDnc6k+bNWIO z@oRK*7~H)g-M%Vx@IzS60B@nQF`BNGh(U2*J7X6lINAAc)>k8 zog-)89Sdh|9Vu|~+85^}wU;g#X`J1SE$^Ng`WHNp2e&m!QeXgYe0k++$FTzx{zr>x)h$PR3?V?C)iI#+%Fm?gM zG|qO$;Lr5>ylkj!MD6auW0%|8pRNT-vDhNs_?TfqP`wF?YPW(~N_69ny>(uLI|-3b z?vC-RU_}cHQKRkfd|j%pvdv{^>rzNjh|N~P$DQUaP8aM_&U||}T4Q-7Y|b8(`d+NF z1(of>ds>%Io*Y8YAV_MLU!T9>cKypezg;F3?;2w7;dnCE(5^S^#kTTfY!|n=;aD6( zHBZH<*0C>-jB!ClivXdP!)~b08Z~*vH4nQNms4={-Ebtm@rb0>DKjkVtagtM6t6rBG2FmBnm$X6*@HRSs5G-Qp=#^$l4(<@v+(~J(UySJm8s~hdV z&iU=L9DUnZ2X#z)bfdc~n&3K3v=~Rd#pTsGx|d%MGXIv8xz_LY!I8@>u#V~UKm~T4 zUg1uhmyV%+l<2l^wK_U6xu}pfKf!HZgTv}t*zM4XxaMaHA&5`$T%lr1v|wDW_OPS6 z=B>~+XS_KO4rlzd^Ce`i3xaZYT+POgr1`XUb5oP^gP7sTe)Sa!3AOO3W|9XV)(KMe2EDgwefcS(nm)o zjK0(K;&OJ5*{|dqc=w_BH_jv_SS@?#p^TCK?-v9V_xYaFiKV1~D7muw@!^Iq6Vm%b zZPpJ!;UK%y0Rr{*YTfoRWwqE@>h9GTZ8TYTs-KT_@P5>Tk08%F#3Fpx=-|eOcgCpV z9y?NZtkcB_uy?jMHaYqD`sUCkR&8C16TRBtfem9a%6#wRlvMm@LepYc+xygE!n;i~ zrqmbwnM_19<^`|%b$F3eh5(DupcA}Mq|u#*Vmdc8eI5f@s=-zCuiJ(a+jUvB^+jsS z_bHFQ#UDJl?OR@(A=G|yQM?lN0@9v`l72PNUSQnnN4w~3*hVv<^T9+^%F=-<;=qvE z*(e)VcW8PRS4=xdht`T^)dw#J=$THQ+#GoMCA~b5nkbkBlh=3JY^=$IhfLqxzh~`r zPH+9S7w5H@K#$i+PStoQz{1U~QN<6H#L2j-UHop(4Q$xX&p+}G$6Pv#HNe>oKt*48 z&%m-7cP6R&G?kytfvsBQ7l0E3?b0glF~a+p3cJTyv)8pCabdFK-A*eE`{R*>oGduJ zXFohh^z?`aPF40iae|&@?0tnU18m+n_aC>~>QRDuk-9RIix6GkCr2lFkLY1LcrYcW zEFOT3xV`WD&DE6-?WWgIi~U*{ZQT^;QU2`olGgt4kPb1|Mq)$1S}|6icxQAdsbw2p z=_n~`t15c%WWm(>#fs`;N_w7!6K3Bhc|pHY6wJ4vqIu~QJPr}86KC%=7brHrSkk)T zl8##S^Mp1ZsY5W1EuJ;G!*Z}?aIPr$M`B=&5Df(!8@ZUstu{ePq5*1{`NZg^y1g_S^yO2 zTU>8N@pwARV2?g(eX@^_&wcm;k7VP!*pg{uU9QCm>xR#@EcF%lGkcm3p!aQ-TpsWL z>)S+B-8Z+-kenPstGOh5pLg>1DERuaii!~!Xv-Gj>;{}4PvNd5GzC5-tNxXP!y8`z zG0ro;-Ku?3O`gomjimbAdO{YaRf1z{xMxXr79GH2l?>I?f@_8a_ojDB%B$%_K?Ukl zP#28Tj^Y(w z_cy;OPZg?N}q+uqe}9*h65UFa4x5}`9T%FE`bBE zI$|=4_PEuXX;VJ2y`{DU)wd%6c2-G#Y7yJm$0$N<#zdZM^Cn_hO&NLPjLeq*VB zVRPHBjqN69<{w*L?c$QNzv{Pd!oJkXOE;a1@9%DXg3WncL+*m0-a}^Z_etK~8}4k+ zgUHSXx9CiYDt&eH*75}6+MA=U6zn)K|Rl#5)8z zSlqp9zve{{Ll96JNLzkH*Cy-FKdOlrfG+QSZvmaH!p3Mh4#Xah4v#-yoI}5)R}CUp zkwlX5P{@7kZ`xy@z<8C=k&HGuS_bfEVrf|FAA+ZWHPHzd{BH`Op<|rx?TF_){iS?( zA;M23IS~=Ney2D=nG^OZ<|EL1@oJlmFp%CU5NR77q`2Ei|BG;v&qSSND;4p4u?^U? z9BiuZf%@q9$-W!XQ)q*LC|3#I)EMw=-8(!%y#n%diq>45=lwFwdb{CPI} z`2zE)r3&=!lPG4Z1T}ir};8o zZ~%y%3%ud=U+w7Dht31LpPrnZLppwPc7#q{EmfsApblIIZv?F<_aOg*J?)SlW(|%S zec3_QQAC=O`}QWgo8A6^o9aa$vHYJ-Z&Er>{3=2I%OuiH)(r1;hn-*%7)9ep7;48feF0uPHKJ ziAdnn_wQHwy+h;&PIQyg@^A21`_o$`ni9M$yscmA$vMz0cEGr*mkvlAAD6VM4L@6QC|c!8q2+_SdbJnupc& zZWn@eeaKPb(SR(3>(nVflU_I(gZN#;yRT*%J9Yfr={XQ1O?C@ehD%D(blf(%50FQ% z1*!An7|~$uNnS$Iz|%RFaYp-~UU{JkiFXl2u+f4$v^q34OVpV+h{DZlU~D`;g=%Oe zF*aKfIpIOEE4aIj#7B{x8AO8F6D-}6iMGQ$3UH0kao@w|<4Pt8mHeU#98l=yj8VeY znyJKLSkqKaRYo?zGW;-f808aDEI!E-$i$rhcNfP27A_1|K+FjEU`@&&r~4kqvQm_k zQ>X16ou23`Gb%)rxqNWJX2{X87mUOY?owzmN;4QG1p0rPah>WtBuqvxP(F*haZ22W zMWTXQ#*W(^B*1}8wiA{sdvTL^43{g3f1#5xh-O*P1TQG)C(INLmS5*G1%&P#_JDnN z%Lx|;(d?8^oL_-y&}|m850|IRc+74QH@5qLR6c_O1kH~79zhB2Tt%j!@*i$&<5*Bh z#k+Gl#;zu&^uXBMeGjt_*LRE-P7G2?Z=~B6fj3d9h>^B+phJ=gTxIutB*a1iQ-Vi>@7hvH`2%k3aJuZ0lN`r1>9vz?R zU**t*OzJ3*&J^MvkXa8UFn-RhGgvI`BxWeCtr$rr)b!A7 zJyYaVFI=v|-3f+p>MqFvoDiwURQfEZgl3=wN~oNNF8CQ?9Bw?>PCdC!Ti~QbDp-T( zxV0=b!L^RG7pAGjQSc3k5VB5)6R_$oHN$F-oQ5|Xvboh}EO@AiJutf}dFG{f zRv-i`0AU;1(ygKC3FAF9lLm1V+#!aAfzb$)%E&B5qOhz}$Es{2eII~D!EwG3hLM>B zE|SBPk%Sx~MhdYSbjTuzCO6=KYDh7+N=oU5t`ybJeUSw(BeI={M6$KmwqtCPsuyPR zj57gqLI?~=KW}*tF%`(B;S^6b*=0 z4Y|T`Yl}vea;%d zELRZO9hce5xl4E(55yX5cz9rdyM$*5N&u*<92AQyN;TvQhpQLa(NN6wF{jy%WM~Xv zpxlk*Br=sO7-p8ufhC!NAqJR-yCK&}8+X4Vr2dCwa~J+egP{JHhOtO4579jM7MM)- z2^12LXgkhn3?3lhl2RB&966a7NTZ%3S%v(V0o1d@Bfi!IgPKb_)rNB5AZvsr@Xr4h#)P!7PxQW@QVq%U0Fp&}!SsK^EjWLQ~ zS{pa^;)htDNl1C2d>hzoMyw9jBN7+3&_}Kf%vvP2G9K9Jb$CeeQfQ-S5Ne?*eUBAl zK_=#=iOjP-l*>&DBDhUNQYS8PgDD_vHsjHWGV<9@L$G9+C8$T=%%Rgl>JXE7*hBLl zZYKE#gzhkE#|LxM-ACr2z{(`NfKzpMq^>iR!gs2JfRiu>OohI-MR$%$V_MfX6AJk? z+;&5dLUKcu=8zv9osg--h@FI)(jgqW$Ar!pDPG$+If)Btc3}rphivSCL(&l={scHh z4(_5O!6D1rjuV}D2@3~&l^ooaJp{J~a2?aT$l^mzsa*%Y7X)_(_||p69VWPPz;)vA zRk8Sx!d}H$N%2Y+4pMmE0mse{D(2p$1CH=r!Tc_Ez`ZB9Sd^K+=zwGA_Ze`MR8P*2 zu_Js}Fu%tf9B(8G2YiJboG_H=Tn{*<4md3$DeF0GFZloO1xn zF4AA()Zz1Ztk}Mzh`Yl$EvD5PHAp*_OX6b|t%jBionc~#2rt80nNuXSIbj~QZ5>0? zKAblRL(4M;8F5z10$`$0dLGCzf6qN)zF}dK2d79jLt*#?5@DuSmCU3F3kM_&mW=GR~ocv|svl5J^WGn~4&qi=6x!f)WWc4|AT9S;s;R(S8}gLD{lK))r;t z9F*ucwhBzBp7zVFT&FTRnHXUH9ez*{Ov1j0DsvT1v@zo#k?spjE(CF4iB<$Qr7)?9 z+lPwB!ZR}IL$sH2Cj~}@H1Z=9=^V!r9OW>#7U*AxFXT@oIPKSb?JYRB=bV}wZ6B{n zOkJO8^_uR!=wViDVKe;5(VFnIuXz#?=VQA0+|V&WcdAzaF*Sf;S^v z@2S}0(Ff8bO_c1ra&&8&f^i}RJZ8XYyI?aGB%_2{cFq$pFUVmhsY#sHbxh6=I>BfU zqAFO{JBr?rN_gyGt3l}m$E+odaNM zedMrhPWEMsQE5ZRxEm6;H_y{!XF_R}qHGdw;-AR7&sp`K zk#vZYO1Um)nZDY&7Wi+L-aJH-1s#X~Vw9OJJJ)H3`+(j~_`HtKEgKsEA?i+ga@f8cJjaJJ|F182^{t()~PTp^F<>U{BEIG#2O zH#g;9I4P2ik(9s2;y-Xmt!47{AO8n#1q)|)@;`8_Oib3z{14nvf?K`sKX6E%!Fxk9 zFa9fVA<~T*fki>oziN4n7OHUOsQ+{#uP+Mk&sN#9dp4$!;y96G@ZuN8Jf1FT>e$u) zQRmVS7Ea6HUzKY?N(m!PoL89SmJn!yTPF{w-b{?al|(HPY*&1yeH}it zW4z^SU!$ilmeUHTp@Xj=`*Hl3AzNNM?F@1oIB=ln!%^1}@*Mg%qIvoYj@xS`QQ#iY zKVb$4^`u4I|GJZjp>vh`L$%ohJ{2=VasAKm5{POq=S+{+h~eo(>sxy{A^n_wlTM4u z%eheEI%dna*A4ZTg9jq)DvwPX>M03=kbPsZY}v9kdno}AS%KYF0Lvs&tH?guc(aX? zbdLERS5}O4p*7ivtZh8wg-92rwlk5zL=@%KM+Vcc*N}&zTzpXrt=Bm$^Nu10KuPY5 z$cNl+DFs`}@iCNL&#>wBHE>o8Zxl>7n>WwXB^EtHW;YY*Ey0ZrEHIMt$w*S_y%gtb zMr2Ipk#g$UoOb3EGM*VWeZCM|8YU7|nT%*A!U#fa9~i+KQX5K>XH(gkXRPcsK`0&a z7y~8heILEAz}qo`F*si_bBIuKbxhx#q+x$!*5v0HHg}#)p3f&I$7DGZQ#?T^c{m&? zi7AvM@6HnHjB^+>2fIr_Eh$OgXwrti5EO9lLGviaO*>=I6{L_8V+>fr*hE0nP!(jssi2Q3`5Q=yv2(`hBQNV=|hX>pe!yF{0@CJhmN)!;v5Rh{1BEl(9 z$7qJ5JEMlEBytGR3L!rvz#~6!?iej;3~_~$;z@Bb^2q~H_B)QE3^Rk=4^>i$sMv~e zOr)U`5y~*YSpvR;^pUdQ1yp&(*r<{lpj>u??(DCTea>WvDP)O|5zc;Im^=GxoG9Q_ zoM`YtVA3KSsTl#Kmol2Y5rCUTOe&ZGEtLHl+gwJ#8c>^l_;o+_*Q8g-P6<;O&JM0d zi;c41BiX^V?qVIYwOcjFRANLHhQnY*(wmZ`1>F~w0CEUIlQ7^gFKC#Vgc9`48s`*cDo+w*;Z?T83;TE&QT_uMiz|)fo2T2VDqf$vpmq5oqlTHgR z_9yTW03Sf$(oU(QexzwQWa2@)46I#dEyDA2SXEl8N!^iz&kH5u?8wattVvf`=-w{mV#Z@GD(qWGb}TX0KpsL%8)?f#3?80TP$bsy&J$~d6rA6~j*f>CWWPR5 zx)SNS0fLDfj$r0ac63u3{Sa>9=_JNP_9Qt8p{Tvs!Od>4gXe}#^CZQL)Ud!z#v<`# z55lvIXbPQwc+m(=#j}I;A=0JB2uqTYVrsIn!~|ymO-C;#ayC0Sq8oit-iya3SSh6< z99WOiEj%?bmPk`Z94UbvZ5*-D3sL?G9Kq223kfw9q}H>~RNWd!-AAH3Yxonp(CDQg zy@2I<#dcD$(kKKH>G3zF{vHuy1Y*o+DmDvBNmP1>;SD2ND|WNpL0l>Oc2zw~`z7j5 zwA)0m-4VW#%0@px-3vP7XCoH;ASrMf=0cTGum=c7Vu?z_rcvz_W0S`!@Fo1bg&pn( zNvQz#LW;Nm!|V}|IZ4CZ3Rw+PWBI-k(W*DBz8kRJL2RNb+kGb69r3=ate6hSx<@0r z#&&mSyMx$7Rt|gSu-%b~^@8oL#;VL95c8QOW-&>%I~YBJW$a*<_Fj>Ve2wFr-uQM$4XBwL!^&;mHNm~iV%u$Gi7b(o-p<0Py0$94IV_L>z zA*`Qj?8wj;P4ZgI>*K^kT-}A-rD=%Ti020M>jCuZvOHQxa2>DJ=tqOcpo768W%ZuD zgkuDF5|r|U7)M7UBqIku&t`wlA(s3cBXRnvz@nOziXj@{%Ntl<^6ekPND7wc%hGVHKM!TFn7!rfk`;sccC1T%w0wgyh+)YCdT(6T})Dx zIQ3vFQ{$dKgCcBm2T+vfT$! zcj9=)Pui%`mE6M7?A#hixg4eV8v_^F>~y5c@2jtTm4gf>t>TSX*1S-now46w0}#Z1XX$4`OVSoo5-Fe5sAP3PM-~{)|_kAj*}<=FsaB zOGz5Pih|n}at(4%&jG zR7$y{!iJ~$`AL~Y{)jZN$vH?L8LboW^R9w<&S%g>3b3s-aRpYSN`AD(f?7JG-1{<$jxl$MS z{@JtMm|*RMKI6x4{BX;C{ralrw5wOge|d7O(h=_gsjz5#C}SQ;MYMw95cWbcb^MixQ_zO4q6sE85+kkg=Tiy1tUZAy@f(taQL z4}XI(Po~s9qD1|0%`t!T+gn{T6ZJ?j6MK+iy0S%bND(c;pUR3zd15+}{`!I@vJy@_ z!CGf`Hlhl0sp1QifjkkJrp441f1Ab}dAf^kY}dmBz^q3QRd})+t~Kk{?ZxZDLy*T& zj6ZR*HvxZT2m-e$8;5A}VX_Y5G?vj>_|r`;NTomm$3P?vuCzyXN)J6@W+LKyAds%J znXoFVprJntfMO8mPxnHVVk0mNd-N1H(QJ8k$f~nPg1%?Tz6sg*^HN$=kyL^|X|RQ_ z2YWP^H3iz_PogNB6RAHgC^VvymTW4OK$3|sU$H@Gav?Q!5{wW6&Pl8VtfHMgUwFi1 z{T`YQWhGNCKFV5x$0U8bKP<%wrABxem^G&b`^lWD;88#AVoKw?cI}#`c~HQV|F6Ak z|ED@z|DV@!j?QaFq{%eZc1e>;Dm5{x;%^&|Cm1K5`; z265>_9r*W?69lAyP0fRu-H#48OEkhqTopDS!Rgpu0 z$R-1Si^K`)O$QY8!SQLEctfTx*VsxDY9TePKtkJGEUzyBD3#RN zu24x&Q^ccwas5JRSpNM^y?Hj>mX-v<=j`^;=DrI8aF8;rsCLJ25Cw?0~^pzLvgSL`H!oqLDTjUpIsnSH#I`DJay!31$0fJ zlCe6w(7j0)z28Zgt!7ByFlS>!!wLdz5xdeU0wim!tOVD9HSPomygNt>ZIC(-mEjU) z2ro+b0|ugF!<0f%#(b2q*8t|^h{6<94rU2+jeCfpw6!4+=TVmuYwK4?bo51*Z6&b{ zvZ7V{fAOq)`gAU#yT&F6$K!_7Pv5HfG1k_1(DJ4I`6*`sc6Vs3@$uuwNVvPAu%ZqA zaYKFet}5p(@%7+YTn&ys3>>AlWNqMdjk_gi?bBFq$-JvTWHhUnId*;xW|UK~?HURs^O|W`mBVcHRU6qW(<2 z@M~3ITZIn~FVvO*_jAloWFBZI{m#>q56puR5OW#|)mjF|#+69yg@Eqvj)j0WJ^hCi zxqOC_!&AVcxyO@~_){@y6#A~~46uRd;?iEVAd&Z0ioRrqR7?+-WLu==XJ8rzibU4F zatPnOJeE?nxPf1w7umTG+;Jx`n-8!RbWPQnQp*>B62esprM3oX`HoIbV`XKM*rk%_c|PA`J(AWEe^u!>7VKsRF_bh* zN*PgOqN2n0!UB$qeC6fiBUm5z@nG1q`g%3Um;n8Qo|EIFVG6+IA=6t4PoCsUJYt2F zt~X{Yy@)ov1EGNla+gGolZT1&OfEY!mU3p~0+7#UT1kT}XT>BnyY@PGj1BjC;8kS)N=hZ8Udqqfu@E6aSPIf^4#T(e-e>!n+m2$dT@n zBO|Y1;^o(|Ad9)ig5TH< zOW53z1Cm?HMr$Chq6CoVOit6qXmuFT8#Xo*+Ov|p8~W%uIaOMg#i+>bPkVAZhD`)9 zvN~E7PE$7W7e?Ncg}_B7vQ;^Zd-FWavyVAt>|6?6qsUdtg)*m1q<6y>Hg+btDKJZQ z+J1QNpcZpg(XW}gq(6f!w@8aZ*Cz52ashW}Mcy8tKE|h!3qGUuwn9zrL^hNuQ||BE zK9H3Nz3oCnM~26;VzwBY?Pk}dD)PL;b}JxNgHyCGqEHj;rdjlqE>`Phl~cLTDGtFY zWo?v`XEE}CoEF*WRv*yOD%dtCAAKuXOPr~LNJg%eOuKVCz21o;2Ds&!Zl>=!c!DAU zP=eTGJ}C;8|62KTx+nGBgGA)cfOulm0$dAC; ze{oIz*^o5t$k=QT`PYr;wB_& zv|CC{%{grAm4Dt$`))Yu`b zpT0=8R_;e>W36hGWVMnx_hTe^v(BL420Q(^Bw{!(S)2y9*FLuNUOs!}=OT`l-m2y` z3)`+EYE4#;^SpK(kf1wOf| zcw3Kh3*L6BR|L6DT02*js`8VoW{dk8K+_1bNaC1VRY8lXm`8BD_Bd@44Xr;sUW*Oby4WO#a1st^IpJ@m^Z~ z_g)!CR0WuPRm#PS7q^!l@{LEyB63pw>pD!0-rrQb-wziqy*@_~Q1&M2HtD5!y?^un za1mVowg6A%^%|bMxZ2uU>aAQdW(&k0mFA-2K$)2pYoURjsVkt zf2f4dRk(l~9%z24^JU5`IDnnic{nXgWSVLqK|razKv&w-ud~Zq??Z+Az5fj9iS_4# zyR&`a-me7SdIsQ|8w`wkM-p|mUD|Tc6aW8$>Ar?f;BHMc8`zi%uPUL!eTIebr~_ad zivma0Y;rP{-Vfmkst|P-rE;D~LlD2vFTlf{VG91L4CL87zOt)HdflGP2M4ARe)P1d zX|shfDaAX;m&C2gfR{)7Q*?AR+~XyG_8idtzrWlUiQ)7@VHfB!(* zh;m@N5pg)U8V~NlY=)HXmoJYE4Go2hGD+{vCCigDymsLErnE|o*70|yn=g@lpx7yw zmAy5sa^R(Hgo(0QpH&PrVs}akfC7*1!Y9VfJ}%jgfy|fx&Rg(ZtlE_frFS$ z=5^6tSC68+Tu1qz;Z))tZOT>=T8^|vwF7kBJ3LMW4;(IkRRNFE?EJEKUax@lCV2O1 z1cTc73k2Vvrj0f9##uS*hVSR>rw9$gNQ2c!0pwpwl^^(gszOEZY$~LIf@q5rL!TVq zpfQJ>>Z!hW=O{y2&zj5Zt`tOe#BH5d;cWcS7hc=-PEJk$8m6vZ30SEb;jk$X#;U5_ z@c-5fC9L{^k|c*BwPB{<2HdP&DR|bd)Lsyng+9C2-sgMc;1Y z;RrIP{GDSTjZM5N-rpoaNxt~B@s34dBJzIzJT*Dy1{}Qd9L#UAW~d6*$2wc&wuXuy zP_7r@>Pv(CcEU$U<(`sZvuMwqJ9l?)^L0W8zitVEf7sN83_fN#(j@ieKZdJ+{ZMtuxb@yK1{;*cQ}DVbjePo}9i*^un0)#q)$s!f&W z20{`N68aIou_zlz&R2RDhLgzv%s~4-8}^3RlLgAW{SRwm_DT^?X?L#J>%RIN9pE1( zDW2r>sdmHs7+&GCpAPmsu0Z$p)&h84V#$5RIT+usB1h_M=TP4|Qx%i0^l*_6YGB1o zhqW8ePFW5Pd?oyYW9CzYDJ2)?Kg6au9N{AJ_StQuU0{A6dxwyakXtIHiTsh;z_O1B zjyiz9gp8T@fUObfsLjCPRdXfY$;q+T&CSS|zr}p;k*hFp;@#lFv|I3rDy!o${!BWn zokOP0$^Q1rN`xd#bBD_SL0*^^7!Y9Mk5$r^t&-B)AU#w^(-^ur?Z!QXtQ4nV^X&h| z=ADAz2UYAD4u$WsJXng=lq49pEFKlUc;WG%bX&6v_-$hM9jMTDPo*r8Pem`(%`gGx zLrVABl%NMld%{Y$yGlL6%$e?*CasYQy#FE4E%tdf7X=c!hf8^2)H@&9Dvg-4Cs@Kn zf$n(-M8=b)yam-(#ybcEP9Axl#lL?nnj#%eLOvDl+i5Ry-NCF0T5eol{iAk*LE?AJpk>{k%@{=^r~^{GiBI+VS+)U$K>seFI&z`y}f?yXyv7UAQbjo6X-*5sj?f#G{ioOka0 zQR=vBR|3%D=SM-=EDU*4h`F!h(}g(S#>VDGE#lYNA-IbrU?=l8+1c5L2D_8jgCCBn zvfqkNObk4?V)=4OYinzwG-zySKZ0uqYtEYm&6J97mAK>A?6iq3_!$`)=T+*Yy}gqU z)aT8c*WXqib7jXh$Ap@inxzr#xENY(kJ0bCdU$x;s7%^VCiS5#EgLs_+-Ra=OcKNx)Z@X{wdUOb=w)n&DO=UamIcGN@p?&dQ= zMMcG-p;wK2T&{sufMK~ z7YObOLxv@tqO_~mug{UT2;+o8VRfg5*`XOrmoDueANAXeUS>Y2bfGo#J${ Date: Tue, 29 Jun 2021 11:31:01 +1000 Subject: [PATCH 095/414] Removing unneed import. --- .../fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx index 562e44fb6c9..e82d36ce5d8 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx @@ -12,7 +12,6 @@ from ctypes import cast, py_object cimport cython from sage.algebras.fusion_rings.fast_parallel_fmats_methods cimport _fmat -from itertools import product from sage.rings.qqbar import QQbar ############### From 5ecf8f8df2bfef9571201da9b543ac58cfb04035 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Wed, 30 Jun 2021 11:06:15 -0700 Subject: [PATCH 096/414] minor docstring edit: link back to FusionRing.get_braid_generators where braid group representations are discussed --- src/sage/algebras/fusion_rings/f_matrix.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 60375e7d49b..2911df25a2d 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -78,7 +78,10 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab :class:`FusionRing` captures much information about a fusion category, but to complete the picture, the F-matrices or 6j-symbols are needed. For example these are required in - order to construct braid group representations. + order to construct braid group representations. This + can be done using the :class:`FusionRing` method + :meth:`FusionRing.get_braid_generators`, which uses + the F-matrix. We only undertake to compute the F-matrix if the :class:`FusionRing` is *multiplicity free* meaning that From 92acd43985dde3139657136e6a4cc2481f0b3aa7 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 14 Jan 2022 17:38:51 -0500 Subject: [PATCH 097/414] Trac #33176: fix Cython warnings in sage.libs.ntl. To fix some Cython warnings like warning: sage/libs/ntl/ntl_ZZ.pyx:274:23: local variable 'ans' referenced before assignment we initialize a few int/long variables to zero when they are declared. These variables are all ultimately passed by reference to NTL to be overwritten, so the existing code is not wrong, but the warnings are unattractive. --- src/sage/libs/ntl/ntl_ZZ.pyx | 2 +- src/sage/libs/ntl/ntl_ZZ_pX.pyx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/libs/ntl/ntl_ZZ.pyx b/src/sage/libs/ntl/ntl_ZZ.pyx index e488b7adc43..3d4134376e4 100644 --- a/src/sage/libs/ntl/ntl_ZZ.pyx +++ b/src/sage/libs/ntl/ntl_ZZ.pyx @@ -270,7 +270,7 @@ cdef class ntl_ZZ(object): AUTHOR: David Harvey (2006-08-05) """ - cdef int ans + cdef int ans = 0 ZZ_conv_to_int(ans, self.x) return ans diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pyx b/src/sage/libs/ntl/ntl_ZZ_pX.pyx index bd34a79c85c..56dbe3ee82b 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pyx @@ -276,7 +276,7 @@ cdef class ntl_ZZ_pX(object): """ self.c.restore_c() cdef ZZ_p_c r - cdef long l + cdef long l = 0 sig_on() r = ZZ_pX_coeff( self.x, i) ZZ_conv_to_long(l, ZZ_p_rep(r)) @@ -1152,7 +1152,7 @@ cdef class ntl_ZZ_pX(object): ZZ_pX_Modulus_build(mod, modulus.x) cdef ntl_ZZ_pX mod_prime cdef ntl_ZZ_pContext_class ctx - cdef long mini, minval + cdef long mini = 0, minval = 0 if Integer(modulus[0].lift()).valuation(p) == 1: eisenstein = True for c in modulus.list()[1:-1]: From 5594ba894abd548e403593378c588a6571fb9db6 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 14 Jan 2022 17:54:25 -0500 Subject: [PATCH 098/414] Trac #33176: update API usage in sage.libs.gap.element. The C_NEW_STRING() macro in GAP's stringobj.h is deprecated (according to the source), and should be replaced with MakeStringWithLen(). So that's what we do. The conversion is straightforward and happens to fix the Cython warnings, warning: sage/libs/gap/element.pyx:267:21: local variable 'result' referenced before assignment warning: sage/libs/gap/element.pyx:268:15: local variable 'result' referenced before assignment because now the return value of MakeStringWithLen() is assigned to the "result" variable. --- src/sage/libs/gap/element.pyx | 2 +- src/sage/libs/gap/gap_includes.pxd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index e6a7b3800e9..c26c80b2cb6 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -264,7 +264,7 @@ cdef Obj make_gap_string(sage_string) except NULL: try: GAP_Enter() b = str_to_bytes(sage_string) - C_NEW_STRING(result, len(b), b) + result = MakeStringWithLen(b, len(b)) return result finally: GAP_Leave() diff --git a/src/sage/libs/gap/gap_includes.pxd b/src/sage/libs/gap/gap_includes.pxd index 5a9ab486f77..6d22e32540b 100644 --- a/src/sage/libs/gap/gap_includes.pxd +++ b/src/sage/libs/gap/gap_includes.pxd @@ -182,4 +182,4 @@ cdef extern from "gap/stringobj.h" nogil: bint IS_STRING(Obj obj) bint IsStringConv(Obj obj) Obj NEW_STRING(Int) - void C_NEW_STRING(Obj new_gap_string, int length, char* c_string) + Obj MakeStringWithLen(const char* buf, size_t len) From 01176df32fc2e748c6e0a6330a22ea8e93547c89 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 14 Jan 2022 18:20:35 -0500 Subject: [PATCH 099/414] Trac #33176: fix a Cython warning in sage.libs.singular.groebner_strategy. In the NCPolynomial_plural normal_form() method, the int "max_ind" is passed by reference into Singular's redNF() function where it is promptly overwritten. This is fine, but Cython cannot know that, and throws warnings: warning: sage/libs/singular/groebner_strategy.pyx:540:67: local variable 'max_ind' referenced before assignment warning: sage/libs/singular/groebner_strategy.pyx:542:32: local variable 'max_ind' referenced before assignment Here we initialize "max_ind" to zero to avoid them. --- src/sage/libs/singular/groebner_strategy.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/libs/singular/groebner_strategy.pyx b/src/sage/libs/singular/groebner_strategy.pyx index 20751eafa71..e5807a7a9d4 100644 --- a/src/sage/libs/singular/groebner_strategy.pyx +++ b/src/sage/libs/singular/groebner_strategy.pyx @@ -536,7 +536,7 @@ cdef class NCGroebnerStrategy(SageObject): if unlikely(self._parent._ring != currRing): rChangeCurrRing(self._parent._ring) - cdef int max_ind + cdef int max_ind = 0 cdef poly *_p = redNF(p_Copy(p._poly, self._parent._ring), max_ind, 0, self._strat) if likely(_p!=NULL): _p = redtailBba(_p, max_ind, self._strat) From 93f2e907d774599444c8b9ff4f3e89a132fbaa6b Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 14 Jan 2022 18:55:23 -0500 Subject: [PATCH 100/414] Trac #33176: fix Cython warning in sage.matrix.matrix_modn_dense_template. The linbox_det() method in this file creates a float/double variable "d" and passes it by reference to the FFLAS/FFPACK pDet() method, which in turn passes it by reference to the Det() method to be overwritten with the return value from that function. This is all fine, but Cython doesn't know what the uninitialized variable is used for and throws warnings like, warning: sage/matrix/matrix_modn_dense_template.pxi:283:19: local variable 'd' referenced before assignment Here we initialize it to zero to avoid the warnings. --- src/sage/matrix/matrix_modn_dense_template.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/matrix/matrix_modn_dense_template.pxi b/src/sage/matrix/matrix_modn_dense_template.pxi index ffdf576df34..9f945e9440b 100644 --- a/src/sage/matrix/matrix_modn_dense_template.pxi +++ b/src/sage/matrix/matrix_modn_dense_template.pxi @@ -274,7 +274,7 @@ cdef inline celement linbox_det(celement modulus, celement* entries, Py_ssize_t cdef ModField *F = new ModField(modulus) cdef celement *cpy = linbox_copy(modulus, entries, n, n) - cdef celement d + cdef celement d = 0 cdef size_t nbthreads nbthreads = Parallelism().get('linbox') From ae1e07083862e25f6df01fc9795d619527468053 Mon Sep 17 00:00:00 2001 From: Daniel Bump <75940445+dwbmscz@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:26:10 -0700 Subject: [PATCH 101/414] fixed import in weyl_characters to build sage --- src/sage/combinat/root_system/weyl_characters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index d2d1d9f967b..56163c5ca85 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -18,7 +18,8 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet from sage.misc.functional import is_even -from sage.rings.integer_ring import ZZ, Integer +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ class WeylCharacterRing(CombinatorialFreeModule): r""" From 10e1361ce2000f9558a2fd377efc476cc6a25f21 Mon Sep 17 00:00:00 2001 From: Daniel Bump Date: Fri, 6 May 2022 12:03:36 -0700 Subject: [PATCH 102/414] revision of doctest in poly_tup_engine --- .../algebras/fusion_rings/poly_tup_engine.pyx | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx index f0e277921c2..be804b4da8b 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -546,36 +546,23 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): term by term (we assume the tuple representation is sorted so that the leading term with respect to the degree reverse lexicographical order comes first). For each term, we first compare degrees, then the monomials - themselves. + themselves. Different polynomials can have the same sortkey. EXAMPLES:: - sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey - sage: R. = PolynomialRing(QQ, order='deglex') - sage: p1 = x*y*z - x**2 + 3/2 - sage: p2 = x*y*z - x*y + 1/2 - sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) - True - sage: R. = PolynomialRing(CyclotomicField(20), order='deglex') - sage: zeta20 = R.base_ring().gen() - sage: p1 = zeta20**2 * x*z**2 - 2*zeta20 - sage: p2 = y**3 + 1/4 - sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) - True - - TESTS:: - - sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup - sage: R. = PolynomialRing(CyclotomicField(20)) - sage: p1 = R.random_element() - sage: p2 = R.random_element() - sage: (p1 < p2) == (poly_tup_sortkey(poly_to_tup(p1)) < poly_tup_sortkey(poly_to_tup(p2))) - True - sage: (p1 > p2) == (poly_tup_sortkey(poly_to_tup(p1)) > poly_tup_sortkey(poly_to_tup(p2))) - True - sage: poly_tup_sortkey(poly_to_tup(p1)) == poly_tup_sortkey(poly_to_tup(p1)) - True + sage: F = CyclotomicField(20) + sage: zeta20 = F.gen() + sage: R. = PolynomialRing(F) + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup + sage: p = (zeta20 + 1)*x^2 + (zeta20^3 + 6)*x*z + (zeta20^2 + 7*zeta20)*z^2 + (2/3*zeta20 + 1/4)*x + y + sage: p1 = poly_to_tup(p); p1 + (((2, 0, 0), zeta20 + 1), + ((1, 0, 1), zeta20^3 + 6), + ((0, 0, 2), zeta20^2 + 7*zeta20), + ((1, 0, 0), 2/3*zeta20 + 1/4), + ((0, 1, 0), 1)) + sage: poly_tup_sortkey(p1) + (2, 0, 2, 2, 0, 1, -2, 1, 2, -2, 2, 1, 0, 1, 1, -1, 1) """ cdef ETuple exp cdef int i, l, nnz From 2fd832c289d60805a1dcdd1eaf1a17bbc2c5b41b Mon Sep 17 00:00:00 2001 From: Daniel Bump Date: Mon, 9 May 2022 14:58:10 -0700 Subject: [PATCH 103/414] default number of strands in test_braid_representation changed from 6 to 4 to speed up --long doctest --- src/sage/algebras/fusion_rings/fusion_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index a3e70b5866e..c9730ac7676 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -377,7 +377,7 @@ def _test_total_q_order(self, **options): tester.assertTrue(tqo.is_real_positive()) tester.assertEqual(tqo**2, self.global_q_dimension(base_coercion=False)) - def test_braid_representation(self, max_strands=6): + def test_braid_representation(self, max_strands=4): """ Check that we can compute valid braid group representations. From cf166d78dc0df38f1e70e6350c9911259a3d0cee Mon Sep 17 00:00:00 2001 From: Daniel Bump Date: Mon, 9 May 2022 15:40:12 -0700 Subject: [PATCH 104/414] fusion_ring: use F41 instead of B22 in test_braid_representation doctest to save time --- src/sage/algebras/fusion_rings/fusion_ring.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index c9730ac7676..05b560e193a 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -377,13 +377,14 @@ def _test_total_q_order(self, **options): tester.assertTrue(tqo.is_real_positive()) tester.assertEqual(tqo**2, self.global_q_dimension(base_coercion=False)) - def test_braid_representation(self, max_strands=4): + def test_braid_representation(self, max_strands=6, anyon=None): """ Check that we can compute valid braid group representations. INPUT: - ``max_strands`` -- (default: 6): maximum number of braid group strands + - ``anyon`` -- (optional) run this test on this particular simple object Create a braid group representation using :meth:`get_braid_generators` and confirms the braid relations. This test indirectly partially @@ -391,15 +392,16 @@ def test_braid_representation(self, max_strands=4): code were incorrect the method would not be deterministic because the fusing anyon is chosen randomly. (A different choice is made for each number of strands tested.) However the doctest is deterministic since - it will always return ``True``. + it will always return ``True``. If the anyon parameter is omitted, + a random anyon is tested for each number of strands up to ``max_strands``. EXAMPLES:: sage: A21 = FusionRing("A2",1) sage: A21.test_braid_representation(max_strands=4) True - sage: B22 = FusionRing("B2",2) # long time - sage: B22.test_braid_representation() # long time + sage: F41 = FusionRing("F4",1) # long time + sage: F41.test_braid_representation() # long time True """ if not self.is_multiplicity_free(): # Braid group representation is not available if self is not multiplicity free @@ -410,10 +412,13 @@ def test_braid_representation(self, max_strands=4): for n_strands in range(3,max_strands+1): #Randomly select a fusing anyon. Skip the identity element, since #its braiding matrices are trivial - while True: - a = b.random_element() - if a != self.one(): - break + if anyon is not None: + a = anyon + else: + while True: + a = b.random_element() + if a != self.one(): + break pow = a ** n_strands d = pow.monomials()[0] #Try to find 'interesting' braid group reps i.e. skip 1-d reps From 2792fdcb973f3af993bcbabe2c7d0947feb913ba Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 28 Jun 2022 14:23:30 -0700 Subject: [PATCH 105/414] fixing doctest failures on f_matrix --- src/sage/algebras/fusion_rings/f_matrix.py | 160 +++++++++--------- .../algebras/fusion_rings/shm_managers.pyx | 3 +- 2 files changed, 83 insertions(+), 80 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 2911df25a2d..07f07eb7949 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -693,6 +693,7 @@ def get_poly_ring(self): """ return self._poly_ring + #TODO: this method is incredibly slow... improve by keeping track of the cyclotomic polynomials, NOT their roots in QQbar def get_non_cyclotomic_roots(self): r""" Return a list of roots that define the extension of the associated @@ -1764,7 +1765,8 @@ def _partition_eqns(self, eqns=None, verbose=True): print(graph.connected_components_sizes()) return partition - def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=60, verbose=True): + #Try lowering larest_comp to 45... This might help w parallelism + def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verbose=True): r""" Compute a Groebner basis for a list of equations partitioned according to their corresponding graph. @@ -1870,6 +1872,7 @@ def _get_component_variety(self,var,eqns): ### Solution method ### ####################### + #TODO: this can probably be improved by constructing a set of defining polynomials and checking, one by one, if it's irreducible over the current field. If it is, we construct an extension. Perhaps it's best to go one by one here... def attempt_number_field_computation(self): r""" Based on the ``CartanType`` of ``self`` and data @@ -2123,8 +2126,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start sage: f.fmats_are_orthogonal() True - sage: f.fvars_are_real() - True + + # sage: f.fvars_are_real() + # True In any case, the F-symbols are obtained as elements of the associated :class:`FusionRing`'s @@ -2211,11 +2215,12 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self._checkpoint(checkpoint,5,verbose=verbose) #Find numeric values for each F-symbol + self.shutdown_worker_pool() self._get_explicit_solution(verbose=verbose) #The calculation was successful, so we may delete checkpoints and shared resources self._chkpt_status = 7 self.clear_equations() - self.shutdown_worker_pool() + if checkpoint: remove("fmatrix_solver_checkpoint_"+self.get_fr_str()+".pickle") if save_results: @@ -2405,61 +2410,61 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o ### Verifications ### ##################### - def certify_pentagons(self,use_mp=True,verbose=False): - r""" - Obtain a certificate of satisfaction for the pentagon equations, - up to floating-point error. - - This method converts the computed F-symbols (available through - :meth:`get_fvars`) to native Python floats and then checks whether - the pentagon equations are satisfied using floating point arithmetic. - - When ``self.FR().basis()`` has many elements, verifying satisfaction - of the pentagon relations exactly using :meth:`get_defining_equations` - with ``option="pentagons"`` may take a long time. This method is - faster, but it cannot provide mathematical guarantees. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("C3", 1)) - sage: f.find_orthogonal_solution() # long time - Computing F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients with 71 variables... - Set up 134 hex and orthogonality constraints... - Partitioned 134 equations into 17 components of size: - [12, 12, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1] - Elimination epoch completed... 10 eqns remain in ideal basis - Elimination epoch completed... 0 eqns remain in ideal basis - Hex elim step solved for 51 / 71 variables - Set up 121 reduced pentagons... - Elimination epoch completed... 18 eqns remain in ideal basis - Elimination epoch completed... 5 eqns remain in ideal basis - Pent elim step solved for 64 / 71 variables - Partitioned 5 equations into 1 components of size: - [4] - Elimination epoch completed... 0 eqns remain in ideal basis - Partitioned 6 equations into 6 components of size: - [1, 1, 1, 1, 1, 1] - Computing appropriate NumberField... - sage: f.certify_pentagons() # long time (~1.5s) - Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients - """ - fvars_copy = deepcopy(self._fvars) - self._fvars = {sextuple: float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} - if use_mp: - pool = Pool() - else: - pool = None - n_proc = pool._processes if pool is not None else 1 - params = [(child_id,n_proc,verbose) for child_id in range(n_proc)] - pe = self._map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) - if np.all(np.isclose(np.array(pe),0,atol=1e-7)): - print("Success!!! Found valid F-symbols for {}".format(self._FR)) - pe = None - else: - print("Ooops... something went wrong... These pentagons remain:") - print(pe) - self._fvars = fvars_copy - return pe + # def certify_pentagons(self,use_mp=True,verbose=False): + # r""" + # Obtain a certificate of satisfaction for the pentagon equations, + # up to floating-point error. + # + # This method converts the computed F-symbols (available through + # :meth:`get_fvars`) to native Python floats and then checks whether + # the pentagon equations are satisfied using floating point arithmetic. + # + # When ``self.FR().basis()`` has many elements, verifying satisfaction + # of the pentagon relations exactly using :meth:`get_defining_equations` + # with ``option="pentagons"`` may take a long time. This method is + # faster, but it cannot provide mathematical guarantees. + # + # EXAMPLES:: + # + # sage: f = FMatrix(FusionRing("C3", 1)) + # sage: f.find_orthogonal_solution() # long time + # Computing F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients with 71 variables... + # Set up 134 hex and orthogonality constraints... + # Partitioned 134 equations into 17 components of size: + # [12, 12, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1] + # Elimination epoch completed... 10 eqns remain in ideal basis + # Elimination epoch completed... 0 eqns remain in ideal basis + # Hex elim step solved for 51 / 71 variables + # Set up 121 reduced pentagons... + # Elimination epoch completed... 18 eqns remain in ideal basis + # Elimination epoch completed... 5 eqns remain in ideal basis + # Pent elim step solved for 64 / 71 variables + # Partitioned 5 equations into 1 components of size: + # [4] + # Elimination epoch completed... 0 eqns remain in ideal basis + # Partitioned 6 equations into 6 components of size: + # [1, 1, 1, 1, 1, 1] + # Computing appropriate NumberField... + # sage: f.certify_pentagons() # long time (~1.5s) + # Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients + # """ + # fvars_copy = deepcopy(self._fvars) + # self._fvars = {sextuple: float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} + # if use_mp: + # pool = Pool() + # else: + # pool = None + # n_proc = pool._processes if pool is not None else 1 + # params = [(child_id,n_proc,verbose) for child_id in range(n_proc)] + # pe = self._map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) + # if np.all(np.isclose(np.array(pe),0,atol=1e-7)): + # print("Success!!! Found valid F-symbols for {}".format(self._FR)) + # pe = None + # else: + # print("Ooops... something went wrong... These pentagons remain:") + # print(pe) + # self._fvars = fvars_copy + # return pe def fmats_are_orthogonal(self): r""" @@ -2481,22 +2486,21 @@ def fmats_are_orthogonal(self): is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) return all(is_orthog) - def fvars_are_real(self): - r""" - Test whether all F-symbols are real. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("A1", 3)) - sage: f.find_orthogonal_solution(verbose=False) # long time - sage: f.fvars_are_real() # long time - True - """ - try: - for k, v in self._fvars.items(): - AA(self._qqbar_embedding(v)) - except ValueError: - print("the F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) - return False - return True - + # def fvars_are_real(self): + # r""" + # Test whether all F-symbols are real. + # + # EXAMPLES:: + # + # sage: f = FMatrix(FusionRing("A1", 3)) + # sage: f.find_orthogonal_solution(verbose=False) # long time + # sage: f.fvars_are_real() # long time + # True + # """ + # try: + # for k, v in self._fvars.items(): + # AA(self._qqbar_embedding(v)) + # except ValueError: + # print("the F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) + # return False + # return True diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 416739f04d2..062a999fec4 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -354,7 +354,7 @@ def make_KSHandler(n_slots,field,init_data): return KSHandler(n_slots,field,init_data=init_data) cdef class FvarsHandler: - def __init__(self, n_slots, field,idx_to_sextuple, init_data={}, use_mp=0, + def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, pids_name=None, name=None, max_terms=20, n_bytes=32): """ Return a shared memory backed dict-like structure to manage the @@ -775,4 +775,3 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: f.shutdown_worker_pool() """ return FvarsHandler(n, field, idx_map, init_data=init_data) - From a4fac33a145f16bb77da2cc6599c5e4902ed8220 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 1 Jul 2022 16:57:16 -0500 Subject: [PATCH 106/414] cleaner version, fmatrix working for fr of rank at most 10 --- src/sage/algebras/fusion_rings/f_matrix.py | 129 +----------------- src/sage/algebras/fusion_rings/fusion_ring.py | 2 +- 2 files changed, 7 insertions(+), 124 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 07f07eb7949..db3d1c7b7f2 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -302,10 +302,6 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self.mp_thresh = 10000 self.pool = None - #TESTS - # self.test_fvars = dict() - # self.test_ks = dict() - ####################### ### Class utilities ### ####################### @@ -595,7 +591,6 @@ def findcases(self,output=False): if output: v = self._poly_ring.gen(i) ret[(a,b,c,d,x,y)] = v - # idx_map[v] = (a, b, c, d, x, y) idx_map[i] = (a, b, c, d, x, y) i += 1 if output: @@ -1213,8 +1208,6 @@ def _restore_state(self, filename): self._chkpt_status = 7 return self._fvars, self._solved, self._ks, self.ideal_basis, self._chkpt_status = state - #TESTS: - # self.test_ks = {i: sq for i, sq in self._ks.items()} self._update_reduction_params() ################# @@ -1580,16 +1573,6 @@ def _update_reduction_params(self, eqns=None): if eqns is None: eqns = self.ideal_basis self._ks.update(eqns) - - #TESTS: - # for i in range(len(eqns)): - # eq_tup = eqns[i] - # if tup_fixes_sq(eq_tup): - # rhs = [-v for v in eq_tup[-1][1]] - # self.test_ks[variables(eq_tup)[0]] = rhs - # for i, sq in self._ks.items(): - # assert sq == self._field(self.test_ks[i]), "{}: OG sq {}, shared sq {}".format(i, sq, self.test_ks[i]) - for i, d in enumerate(get_variables_degrees(eqns,self._poly_ring.ngens())): self._var_degs[i] = d self._nnz = self._get_known_nonz() @@ -1631,8 +1614,6 @@ def _triangular_elim(self, eqns=None, verbose=True): _backward_subs(self) #Compute new reduction params and update eqns self._update_reduction_params(eqns=eqns) - # n = len(eqns) // worker_pool._processes ** 2 + 1 if worker_pool is not None else len(eqns) - # eqns = [eqns[i:i+n] for i in range(0,len(eqns),n)] if self.pool is not None and len(eqns) > self.mp_thresh: n = self.pool._processes chunks = [[] for i in range(n)] @@ -1765,7 +1746,6 @@ def _partition_eqns(self, eqns=None, verbose=True): print(graph.connected_components_sizes()) return partition - #Try lowering larest_comp to 45... This might help w parallelism def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verbose=True): r""" Compute a Groebner basis for a list of equations partitioned @@ -1803,22 +1783,9 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb if eqns is None: eqns = self.ideal_basis small_comps = list() temp_eqns = list() - - # #For informative print statement - # nmax = self.largest_fmat_size() - # vars_by_size = list() - # for i in range(nmax+1): - # vars_by_size.append(self.get_fvars_by_size(i)) - for comp, comp_eqns in self._partition_eqns(eqns=eqns,verbose=verbose).items(): #Check if component is too large to process if len(comp) > largest_comp: - # fmat_size = 0 - # #For informative print statement - # for i in range(1,nmax+1): - # if set(comp).issubset(vars_by_size[i]): - # fmat_size = i - # print("Component of size {} with vars in F-mats of size {} is too large to find GB".format(len(comp),fmat_size)) temp_eqns.extend(comp_eqns) else: small_comps.append(comp_eqns) @@ -2127,9 +2094,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start sage: f.fmats_are_orthogonal() True - # sage: f.fvars_are_real() - # True - In any case, the F-symbols are obtained as elements of the associated :class:`FusionRing`'s :class:`Cyclotomic field`, @@ -2164,13 +2128,8 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) + #Unzip _fvars and link to shared_memory structure if using multiprocessing - #TESTS: - # for k, v in self._fvars.items(): - # if isinstance(v, tuple): - # self.test_fvars[k] = v - # else: - # self.test_fvars[k] = poly_to_tup(v) if use_mp and loads_shared_memory: self._fvars = self._shared_fvars else: @@ -2191,14 +2150,14 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if self._chkpt_status < 3: #Set up pentagon equations in parallel self.get_defining_equations('pentagons',output=False) - self.ideal_basis.sort(key=poly_tup_sortkey) #Report progress if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) self._checkpoint(checkpoint,3,verbose=verbose) - #Simplify and eliminate variables if self._chkpt_status < 4: + #Simplify and eliminate variables + self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(verbose=verbose) #Report progress if verbose: @@ -2213,14 +2172,13 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(verbose=verbose) self._checkpoint(checkpoint,5,verbose=verbose) + self.shutdown_worker_pool() #Find numeric values for each F-symbol - self.shutdown_worker_pool() self._get_explicit_solution(verbose=verbose) - #The calculation was successful, so we may delete checkpoints and shared resources + #The calculation was successful, so we may delete checkpoints self._chkpt_status = 7 self.clear_equations() - if checkpoint: remove("fmatrix_solver_checkpoint_"+self.get_fr_str()+".pickle") if save_results: @@ -2410,63 +2368,7 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o ### Verifications ### ##################### - # def certify_pentagons(self,use_mp=True,verbose=False): - # r""" - # Obtain a certificate of satisfaction for the pentagon equations, - # up to floating-point error. - # - # This method converts the computed F-symbols (available through - # :meth:`get_fvars`) to native Python floats and then checks whether - # the pentagon equations are satisfied using floating point arithmetic. - # - # When ``self.FR().basis()`` has many elements, verifying satisfaction - # of the pentagon relations exactly using :meth:`get_defining_equations` - # with ``option="pentagons"`` may take a long time. This method is - # faster, but it cannot provide mathematical guarantees. - # - # EXAMPLES:: - # - # sage: f = FMatrix(FusionRing("C3", 1)) - # sage: f.find_orthogonal_solution() # long time - # Computing F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients with 71 variables... - # Set up 134 hex and orthogonality constraints... - # Partitioned 134 equations into 17 components of size: - # [12, 12, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1] - # Elimination epoch completed... 10 eqns remain in ideal basis - # Elimination epoch completed... 0 eqns remain in ideal basis - # Hex elim step solved for 51 / 71 variables - # Set up 121 reduced pentagons... - # Elimination epoch completed... 18 eqns remain in ideal basis - # Elimination epoch completed... 5 eqns remain in ideal basis - # Pent elim step solved for 64 / 71 variables - # Partitioned 5 equations into 1 components of size: - # [4] - # Elimination epoch completed... 0 eqns remain in ideal basis - # Partitioned 6 equations into 6 components of size: - # [1, 1, 1, 1, 1, 1] - # Computing appropriate NumberField... - # sage: f.certify_pentagons() # long time (~1.5s) - # Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients - # """ - # fvars_copy = deepcopy(self._fvars) - # self._fvars = {sextuple: float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} - # if use_mp: - # pool = Pool() - # else: - # pool = None - # n_proc = pool._processes if pool is not None else 1 - # params = [(child_id,n_proc,verbose) for child_id in range(n_proc)] - # pe = self._map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) - # if np.all(np.isclose(np.array(pe),0,atol=1e-7)): - # print("Success!!! Found valid F-symbols for {}".format(self._FR)) - # pe = None - # else: - # print("Ooops... something went wrong... These pentagons remain:") - # print(pe) - # self._fvars = fvars_copy - # return pe - - def fmats_are_orthogonal(self): +def fmats_are_orthogonal(self): r""" Verify that all F-matrices are orthogonal. @@ -2485,22 +2387,3 @@ def fmats_are_orthogonal(self): mat = self.fmatrix(a,b,c,d) is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) return all(is_orthog) - - # def fvars_are_real(self): - # r""" - # Test whether all F-symbols are real. - # - # EXAMPLES:: - # - # sage: f = FMatrix(FusionRing("A1", 3)) - # sage: f.find_orthogonal_solution(verbose=False) # long time - # sage: f.fvars_are_real() # long time - # True - # """ - # try: - # for k, v in self._fvars.items(): - # AA(self._qqbar_embedding(v)) - # except ValueError: - # print("the F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) - # return False - # return True diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index 05b560e193a..75b28678c15 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -957,7 +957,7 @@ def r_matrix(self, i, j, k, base_coercion=True): True """ if self.Nk_ij(i, j, k) == 0: - return 0 + return self.field().zero() if i != j: ret = self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2, base_coercion=False) else: From f13165f73ed6dfa338366ac4c179b682dbc771bf Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 1 Jul 2022 17:31:45 -0500 Subject: [PATCH 107/414] adding back fvars_real and certify_methods, not testing due to cypari issues in doctest framework --- src/sage/algebras/fusion_rings/f_matrix.py | 101 ++++++++++++++++++--- 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index db3d1c7b7f2..49e814f20f9 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -2368,22 +2368,97 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o ### Verifications ### ##################### -def fmats_are_orthogonal(self): + def fmats_are_orthogonal(self): + r""" + Verify that all F-matrices are orthogonal. + + This method should always return ``True`` when called after running + :meth:`find_orthogonal_solution`. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("D4", 1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: f.fmats_are_orthogonal() + True + """ + is_orthog = [] + for a,b,c,d in product(self._FR.basis(),repeat=4): + mat = self.fmatrix(a,b,c,d) + is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) + return all(is_orthog) + + def fvars_are_real(self): + r""" + Test whether all F-symbols are real. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1", 3)) + sage: f.find_orthogonal_solution(verbose=False) # long time + sage: f.fvars_are_real() # not tested (cypari issue in doctesting framework) + True + """ + try: + for k, v in self._fvars.items(): + AA(self._qqbar_embedding(v)) + except ValueError: + print("the F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) + return False + return True + + def certify_pentagons(self,use_mp=True,verbose=False): r""" - Verify that all F-matrices are orthogonal. + Obtain a certificate of satisfaction for the pentagon equations, + up to floating-point error. + + This method converts the computed F-symbols (available through + :meth:`get_fvars`) to native Python floats and then checks whether + the pentagon equations are satisfied using floating point arithmetic. - This method should always return ``True`` when called after running - :meth:`find_orthogonal_solution`. + When ``self.FR().basis()`` has many elements, verifying satisfaction + of the pentagon relations exactly using :meth:`get_defining_equations` + with ``option="pentagons"`` may take a long time. This method is + faster, but it cannot provide mathematical guarantees. EXAMPLES:: - sage: f = FMatrix(FusionRing("D4", 1)) - sage: f.find_orthogonal_solution(verbose=False) - sage: f.fmats_are_orthogonal() - True + sage: f = FMatrix(FusionRing("C3", 1)) + sage: f.find_orthogonal_solution() # long time + Computing F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients with 71 variables... + Set up 134 hex and orthogonality constraints... + Partitioned 134 equations into 17 components of size: + [12, 12, 6, 6, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1] + Elimination epoch completed... 10 eqns remain in ideal basis + Elimination epoch completed... 0 eqns remain in ideal basis + Hex elim step solved for 51 / 71 variables + Set up 121 reduced pentagons... + Elimination epoch completed... 18 eqns remain in ideal basis + Elimination epoch completed... 5 eqns remain in ideal basis + Pent elim step solved for 64 / 71 variables + Partitioned 5 equations into 1 components of size: + [4] + Elimination epoch completed... 0 eqns remain in ideal basis + Partitioned 6 equations into 6 components of size: + [1, 1, 1, 1, 1, 1] + Computing appropriate NumberField... + sage: f.certify_pentagons() # not tested (long time ~1.5s, cypari issue in doctesting framework) + Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients """ - is_orthog = [] - for a,b,c,d in product(self._FR.basis(),repeat=4): - mat = self.fmatrix(a,b,c,d) - is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) - return all(is_orthog) + fvars_copy = deepcopy(self._fvars) + self._fvars = {sextuple: float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} + if use_mp: + pool = Pool() + else: + pool = None + n_proc = pool._processes if pool is not None else 1 + params = [(child_id,n_proc,verbose) for child_id in range(n_proc)] + pe = self._map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) + if np.all(np.isclose(np.array(pe),0,atol=1e-7)): + print("Success!!! Found valid F-symbols for {}".format(self._FR)) + pe = None + else: + print("Ooops... something went wrong... These pentagons remain:") + print(pe) + self._fvars = fvars_copy + return pe From 31b72817869d2ac418686b00f9c251b8758abd11 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 16:53:14 -0700 Subject: [PATCH 108/414] build/pkgs/scipy: Update to 1.9.0rc1 --- build/pkgs/scipy/checksums.ini | 6 +++--- build/pkgs/scipy/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/scipy/checksums.ini b/build/pkgs/scipy/checksums.ini index 25b470f4624..11730409e42 100644 --- a/build/pkgs/scipy/checksums.ini +++ b/build/pkgs/scipy/checksums.ini @@ -1,5 +1,5 @@ tarball=scipy-VERSION.tar.gz -sha1=6dfee9fe5f021409b4d294b0a7d9da05b810d207 -md5=df5ce79288fc457238aeef18e8f70dfc -cksum=3909760197 +sha1=f59a225dc4e41f19e0c69bcb4722163f08eb7ce1 +md5=3fe21eea99de291a468c64d218dae0a4 +cksum=2309164171 upstream_url=https://pypi.io/packages/source/s/scipy/scipy-VERSION.tar.gz diff --git a/build/pkgs/scipy/package-version.txt b/build/pkgs/scipy/package-version.txt index a8fdfda1c78..6ed8c32eb58 100644 --- a/build/pkgs/scipy/package-version.txt +++ b/build/pkgs/scipy/package-version.txt @@ -1 +1 @@ -1.8.1 +1.9.0rc1 From dfbf360eb04ef3fc07ace91ace01ef2b52bd766c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:02:31 -0700 Subject: [PATCH 109/414] build/pkgs/scipy/patches/boost_math_tools_config.patch: Remove (upstreamed) --- .../pkgs/scipy/patches/boost_math_tools_config.patch | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 build/pkgs/scipy/patches/boost_math_tools_config.patch diff --git a/build/pkgs/scipy/patches/boost_math_tools_config.patch b/build/pkgs/scipy/patches/boost_math_tools_config.patch deleted file mode 100644 index 6c13393c407..00000000000 --- a/build/pkgs/scipy/patches/boost_math_tools_config.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/scipy/_lib/boost/boost/math/tools/config.hpp 2021-11-01 02:28:55 UTC -+++ b/scipy/_lib/boost/boost/math/tools/config.hpp -@@ -28,7 +28,7 @@ - - #include - --#if (defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__EMSCRIPTEN__)\ -+#if (defined(__NetBSD__) || defined(__EMSCRIPTEN__)\ - || (defined(__hppa) && !defined(__OpenBSD__)) || (defined(__NO_LONG_DOUBLE_MATH) && (DBL_MANT_DIG != LDBL_MANT_DIG))) \ - && !defined(BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS) - # define BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS From 9d384841b03355d4bc83238cf8a13bb98a589124 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:06:15 -0700 Subject: [PATCH 110/414] build/pkgs/meson_python: New, scipy build dep --- build/pkgs/meson_python/SPKG.rst | 16 ++++++++++++++++ build/pkgs/meson_python/checksums.ini | 5 +++++ build/pkgs/meson_python/dependencies | 4 ++++ build/pkgs/meson_python/install-requires.txt | 1 + build/pkgs/meson_python/package-version.txt | 1 + build/pkgs/meson_python/spkg-install.in | 2 ++ build/pkgs/meson_python/type | 1 + build/pkgs/scipy/dependencies | 2 +- 8 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/meson_python/SPKG.rst create mode 100644 build/pkgs/meson_python/checksums.ini create mode 100644 build/pkgs/meson_python/dependencies create mode 100644 build/pkgs/meson_python/install-requires.txt create mode 100644 build/pkgs/meson_python/package-version.txt create mode 100644 build/pkgs/meson_python/spkg-install.in create mode 100644 build/pkgs/meson_python/type diff --git a/build/pkgs/meson_python/SPKG.rst b/build/pkgs/meson_python/SPKG.rst new file mode 100644 index 00000000000..d467894289c --- /dev/null +++ b/build/pkgs/meson_python/SPKG.rst @@ -0,0 +1,16 @@ +meson_python: Meson Python build backend (PEP 517) +================================================== + +Description +----------- + +Meson Python build backend (PEP 517) + +License +------- + +Upstream Contact +---------------- + +https://pypi.org/project/meson-python/ + diff --git a/build/pkgs/meson_python/checksums.ini b/build/pkgs/meson_python/checksums.ini new file mode 100644 index 00000000000..e0c1b8ca4cc --- /dev/null +++ b/build/pkgs/meson_python/checksums.ini @@ -0,0 +1,5 @@ +tarball=meson_python-VERSION.tar.gz +sha1=5472571439c7e40b15fa3cc35913db4e01a34ef7 +md5=409ded258744d5251349d729e5a29047 +cksum=3031327791 +upstream_url=https://pypi.io/packages/source/m/meson_python/meson_python-VERSION.tar.gz diff --git a/build/pkgs/meson_python/dependencies b/build/pkgs/meson_python/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/meson_python/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/meson_python/install-requires.txt b/build/pkgs/meson_python/install-requires.txt new file mode 100644 index 00000000000..9705cab644e --- /dev/null +++ b/build/pkgs/meson_python/install-requires.txt @@ -0,0 +1 @@ +meson-python diff --git a/build/pkgs/meson_python/package-version.txt b/build/pkgs/meson_python/package-version.txt new file mode 100644 index 00000000000..a918a2aa18d --- /dev/null +++ b/build/pkgs/meson_python/package-version.txt @@ -0,0 +1 @@ +0.6.0 diff --git a/build/pkgs/meson_python/spkg-install.in b/build/pkgs/meson_python/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/meson_python/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/meson_python/type b/build/pkgs/meson_python/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/meson_python/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/scipy/dependencies b/build/pkgs/scipy/dependencies index 5cd74c1c23a..943fa47e111 100644 --- a/build/pkgs/scipy/dependencies +++ b/build/pkgs/scipy/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) $(BLAS) gfortran numpy pybind11 pythran | $(PYTHON_TOOLCHAIN) +$(PYTHON) $(BLAS) gfortran numpy pybind11 pythran | $(PYTHON_TOOLCHAIN) meson_python ---------- All lines of this file are ignored except the first. From 2f70caefa2c29f53e66e13717632183a55b2800b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:14:07 -0700 Subject: [PATCH 111/414] build/pkgs/meson: New --- build/pkgs/meson/SPKG.rst | 18 ++++++++++++++++++ build/pkgs/meson/checksums.ini | 5 +++++ build/pkgs/meson/dependencies | 4 ++++ build/pkgs/meson/install-requires.txt | 1 + build/pkgs/meson/package-version.txt | 1 + build/pkgs/meson/spkg-install.in | 2 ++ build/pkgs/meson/type | 1 + 7 files changed, 32 insertions(+) create mode 100644 build/pkgs/meson/SPKG.rst create mode 100644 build/pkgs/meson/checksums.ini create mode 100644 build/pkgs/meson/dependencies create mode 100644 build/pkgs/meson/install-requires.txt create mode 100644 build/pkgs/meson/package-version.txt create mode 100644 build/pkgs/meson/spkg-install.in create mode 100644 build/pkgs/meson/type diff --git a/build/pkgs/meson/SPKG.rst b/build/pkgs/meson/SPKG.rst new file mode 100644 index 00000000000..6a586f562b6 --- /dev/null +++ b/build/pkgs/meson/SPKG.rst @@ -0,0 +1,18 @@ +meson: A high performance build system +====================================== + +Description +----------- + +A high performance build system + +License +------- + +Apache License, Version 2.0 + +Upstream Contact +---------------- + +https://pypi.org/project/meson/ + diff --git a/build/pkgs/meson/checksums.ini b/build/pkgs/meson/checksums.ini new file mode 100644 index 00000000000..e3b435cc812 --- /dev/null +++ b/build/pkgs/meson/checksums.ini @@ -0,0 +1,5 @@ +tarball=meson-VERSION.tar.gz +sha1=25a82635f5b4e43d3b126ecaaccc99bfff575a92 +md5=e6521b43730901bdd67afa2adefa64a3 +cksum=1062989682 +upstream_url=https://pypi.io/packages/source/m/meson/meson-VERSION.tar.gz diff --git a/build/pkgs/meson/dependencies b/build/pkgs/meson/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/meson/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/meson/install-requires.txt b/build/pkgs/meson/install-requires.txt new file mode 100644 index 00000000000..0b9952eaa15 --- /dev/null +++ b/build/pkgs/meson/install-requires.txt @@ -0,0 +1 @@ +meson diff --git a/build/pkgs/meson/package-version.txt b/build/pkgs/meson/package-version.txt new file mode 100644 index 00000000000..92c648bd8c3 --- /dev/null +++ b/build/pkgs/meson/package-version.txt @@ -0,0 +1 @@ +0.62.2 diff --git a/build/pkgs/meson/spkg-install.in b/build/pkgs/meson/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/meson/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/meson/type b/build/pkgs/meson/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/meson/type @@ -0,0 +1 @@ +standard From ade4413e48ae18bf34690e79bdaaa61bc0be0500 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:14:57 -0700 Subject: [PATCH 112/414] build/pkgs/pyproject_metadata: New --- build/pkgs/pyproject_metadata/SPKG.rst | 18 ++++++++++++++++++ build/pkgs/pyproject_metadata/checksums.ini | 5 +++++ build/pkgs/pyproject_metadata/dependencies | 4 ++++ .../pyproject_metadata/install-requires.txt | 1 + .../pyproject_metadata/package-version.txt | 1 + build/pkgs/pyproject_metadata/spkg-install.in | 2 ++ build/pkgs/pyproject_metadata/type | 1 + 7 files changed, 32 insertions(+) create mode 100644 build/pkgs/pyproject_metadata/SPKG.rst create mode 100644 build/pkgs/pyproject_metadata/checksums.ini create mode 100644 build/pkgs/pyproject_metadata/dependencies create mode 100644 build/pkgs/pyproject_metadata/install-requires.txt create mode 100644 build/pkgs/pyproject_metadata/package-version.txt create mode 100644 build/pkgs/pyproject_metadata/spkg-install.in create mode 100644 build/pkgs/pyproject_metadata/type diff --git a/build/pkgs/pyproject_metadata/SPKG.rst b/build/pkgs/pyproject_metadata/SPKG.rst new file mode 100644 index 00000000000..6c0300e9a16 --- /dev/null +++ b/build/pkgs/pyproject_metadata/SPKG.rst @@ -0,0 +1,18 @@ +pyproject_metadata: PEP 621 metadata parsing +============================================ + +Description +----------- + +PEP 621 metadata parsing + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/pyproject-metadata/ + diff --git a/build/pkgs/pyproject_metadata/checksums.ini b/build/pkgs/pyproject_metadata/checksums.ini new file mode 100644 index 00000000000..da299c46588 --- /dev/null +++ b/build/pkgs/pyproject_metadata/checksums.ini @@ -0,0 +1,5 @@ +tarball=pyproject-metadata-VERSION.tar.gz +sha1=5421824aa29786bde43f510365c4d035a0614ba5 +md5=85fcbd5d777809ca2217a996e06fe2e0 +cksum=1327227039 +upstream_url=https://pypi.io/packages/source/p/pyproject_metadata/pyproject-metadata-VERSION.tar.gz diff --git a/build/pkgs/pyproject_metadata/dependencies b/build/pkgs/pyproject_metadata/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/pyproject_metadata/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/pyproject_metadata/install-requires.txt b/build/pkgs/pyproject_metadata/install-requires.txt new file mode 100644 index 00000000000..7ca7140f9d4 --- /dev/null +++ b/build/pkgs/pyproject_metadata/install-requires.txt @@ -0,0 +1 @@ +pyproject-metadata diff --git a/build/pkgs/pyproject_metadata/package-version.txt b/build/pkgs/pyproject_metadata/package-version.txt new file mode 100644 index 00000000000..8f0916f768f --- /dev/null +++ b/build/pkgs/pyproject_metadata/package-version.txt @@ -0,0 +1 @@ +0.5.0 diff --git a/build/pkgs/pyproject_metadata/spkg-install.in b/build/pkgs/pyproject_metadata/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/pyproject_metadata/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/pyproject_metadata/type b/build/pkgs/pyproject_metadata/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/pyproject_metadata/type @@ -0,0 +1 @@ +standard From cafd1c30ed2c66a2cdee89b859be606592999a5c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:20:36 -0700 Subject: [PATCH 113/414] build/pkgs/ninja_build/type: Change to standard --- build/pkgs/ninja_build/type | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/ninja_build/type b/build/pkgs/ninja_build/type index 134d9bc32d5..a6a7b9cd726 100644 --- a/build/pkgs/ninja_build/type +++ b/build/pkgs/ninja_build/type @@ -1 +1 @@ -optional +standard From 6ae3f1e01f5c7b6b8d663e161f825d191a28a110 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:24:55 -0700 Subject: [PATCH 114/414] build/pkgs/pyproject_metadata/dependencies: Update --- build/pkgs/pyproject_metadata/dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/pyproject_metadata/dependencies b/build/pkgs/pyproject_metadata/dependencies index 0738c2d7777..6d5368db738 100644 --- a/build/pkgs/pyproject_metadata/dependencies +++ b/build/pkgs/pyproject_metadata/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) +$(PYTHON) packaging pyparsing | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. From fb2039f4366ec1635deb8979ffebcd007b668d59 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:32:48 -0700 Subject: [PATCH 115/414] build/pkgs/meson_python: Update dependencies, spkg-install --- build/pkgs/meson_python/dependencies | 5 ++++- build/pkgs/meson_python/spkg-install.in | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build/pkgs/meson_python/dependencies b/build/pkgs/meson_python/dependencies index 0738c2d7777..10426eb880a 100644 --- a/build/pkgs/meson_python/dependencies +++ b/build/pkgs/meson_python/dependencies @@ -1,4 +1,7 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) +$(PYTHON) meson pyproject_metadata tomli ninja_build | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. + +meson_python actually declares a dependency on ninja, the Python distribution package. +But it only needs the ninja executable. diff --git a/build/pkgs/meson_python/spkg-install.in b/build/pkgs/meson_python/spkg-install.in index 37ac1a53437..b3bbe7b8f3e 100644 --- a/build/pkgs/meson_python/spkg-install.in +++ b/build/pkgs/meson_python/spkg-install.in @@ -1,2 +1,2 @@ cd src -sdh_pip_install . +sdh_pip_install --no-build-isolation --no-deps . From d9a0a2338d347815532c04e91de0e5e2c6a51914 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 17:34:03 -0700 Subject: [PATCH 116/414] build/pkgs/scipy/spkg-install.in: Use --no-build-isolation to avoid hardcoded numpy version --- build/pkgs/scipy/spkg-install.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/scipy/spkg-install.in b/build/pkgs/scipy/spkg-install.in index 4f359e7363f..c9374839152 100644 --- a/build/pkgs/scipy/spkg-install.in +++ b/build/pkgs/scipy/spkg-install.in @@ -41,4 +41,4 @@ UMFPACK="None"; export UMFPACK cd src/ # Install: -sdh_pip_install . +sdh_pip_install --no-build-isolation . From 05d5109485193d057473d905fabc92a8a55e1344 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 2 Jul 2022 18:17:16 -0700 Subject: [PATCH 117/414] build/pkgs/scipy/dependencies: Add cython --- build/pkgs/scipy/dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/scipy/dependencies b/build/pkgs/scipy/dependencies index 943fa47e111..5d42789eb10 100644 --- a/build/pkgs/scipy/dependencies +++ b/build/pkgs/scipy/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) $(BLAS) gfortran numpy pybind11 pythran | $(PYTHON_TOOLCHAIN) meson_python +$(PYTHON) $(BLAS) gfortran numpy pybind11 cython pythran | $(PYTHON_TOOLCHAIN) meson_python ---------- All lines of this file are ignored except the first. From 463660d95210d6d014c24e1742ab31ed1b0016c4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Jul 2022 10:53:25 -0700 Subject: [PATCH 118/414] build/pkgs/scipy/spkg-install.in: Remove all old compiler/linker settings, add workaround for https://github.com/scipy/scipy/issues/16536 --- build/pkgs/scipy/spkg-install.in | 52 +++++++------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/build/pkgs/scipy/spkg-install.in b/build/pkgs/scipy/spkg-install.in index c9374839152..35c80c027b0 100644 --- a/build/pkgs/scipy/spkg-install.in +++ b/build/pkgs/scipy/spkg-install.in @@ -1,44 +1,14 @@ -# These flags confuse numpy's distutils. In particular, -# if they are set empty bad things happen. -unset CFLAGS CXXFLAGS SHAREDFLAGS -echo "Note: CFLAGS, CXXFLAGS and SHAREDFLAGS are taken from distutils," -echo " so their current settings are ignored." - -if [ "$UNAME" = "Darwin" ]; then - unset ATLAS - unset BLAS - unset LAPACK - export LDFLAGS="-bundle -undefined dynamic_lookup $LDFLAGS" - export CPPFLAGS="-D__ACCELERATE__ $CPPFLAGS" -else - export {ATLAS,PTATLAS,OPENBLAS,MKL,MKLROOT}=None - export LDFLAGS="-shared $LDFLAGS" -fi - -# Make sure that the fortran objects are compiled with -fPIC -export FFLAGS="$FFLAGS -fPIC" -export FCFLAGS="$FCFLAGS -fPIC" - -if [ "$UNAME" = "CYGWIN" ]; then - # Trac #30643 - export CPPFLAGS="${CPPFLAGS} -D_GNU_SOURCE" -fi - -if [ "$UNAME" = "CYGWIN" -a "$SAGE_DEBUG" = "yes" ]; then - # Needed for just one or two modules when compiling in debug mode - # Otherwise the debug symbols create too many sections in the binary - export CPPFLAGS="$CPPFLAGS -Wa,-mbig-obj" -fi - - -# This avoids problems on some systems -- until we officially -# support umfpack (which we will likely do, since cvxopt I think includes it): -UMFPACK="None"; export UMFPACK -# See http://projects.scipy.org/pipermail/scipy-user/2006-July/008661.html -# (Currently SWIG gets invoked by scipy when building the umfpack interface, -# which is bad.) +# https://github.com/scipy/scipy/issues/16536 - meson breaks when CXX="g++ -std=gnu++11" +export CXX=$(echo "$CXX" | sed 's/-std=[a-z0-9+]*//g') cd src/ -# Install: -sdh_pip_install --no-build-isolation . +# Install: "pip wheel" fails for this package with +# AttributeError: module 'mesonpy' has no attribute 'prepare_metadata_for_build_wheel'. +# We can use "build" instead. +# (However, according to https://github.com/FFY00/meson-python/issues/55 this is just a +# consequence of an earlier error.) +python3 -m build --wheel --no-isolation . +sdh_store_and_pip_install_wheel . + +#sdh_pip_install --no-build-isolation . From 0099213180402f0e5f6ad8b1aeb6372fe0967823 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Jul 2022 11:14:33 -0700 Subject: [PATCH 119/414] build/pkgs/scipy/spkg-install.in: Relax version pinning of numpy in pyproject.toml --- build/pkgs/scipy/spkg-install.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/pkgs/scipy/spkg-install.in b/build/pkgs/scipy/spkg-install.in index 35c80c027b0..6b2df258d1b 100644 --- a/build/pkgs/scipy/spkg-install.in +++ b/build/pkgs/scipy/spkg-install.in @@ -3,6 +3,10 @@ export CXX=$(echo "$CXX" | sed 's/-std=[a-z0-9+]*//g') cd src/ +# mesonpy enforces the build-system requirements, including the strict version pins of numpy +# even when --no-isolation (--no-build-isolation) is in used. We patch it out. +sed -i.bak '/build-system/,/project/s/^ *"numpy.*/ "numpy",/' pyproject.toml + # Install: "pip wheel" fails for this package with # AttributeError: module 'mesonpy' has no attribute 'prepare_metadata_for_build_wheel'. # We can use "build" instead. From 1ca943f265207965de883fd8241a7ab7bae84126 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Jul 2022 11:39:23 -0700 Subject: [PATCH 120/414] build/pkgs/meson_python/patches: Add https://github.com/FFY00/meson-python/pull/90 --- build/pkgs/meson_python/package-version.txt | 2 +- build/pkgs/meson_python/patches/90.patch | 31 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/meson_python/patches/90.patch diff --git a/build/pkgs/meson_python/package-version.txt b/build/pkgs/meson_python/package-version.txt index a918a2aa18d..61d13b8d467 100644 --- a/build/pkgs/meson_python/package-version.txt +++ b/build/pkgs/meson_python/package-version.txt @@ -1 +1 @@ -0.6.0 +0.6.0.p1 diff --git a/build/pkgs/meson_python/patches/90.patch b/build/pkgs/meson_python/patches/90.patch new file mode 100644 index 00000000000..5f51ea9aa92 --- /dev/null +++ b/build/pkgs/meson_python/patches/90.patch @@ -0,0 +1,31 @@ +From 055d7b88b815106a2281320668f218c012eda1ad Mon Sep 17 00:00:00 2001 +From: Ralf Gommers +Date: Sun, 3 Jul 2022 13:34:13 +0200 +Subject: [PATCH] BUG: remove `-Dstrip=true` default, it doesn't work on macOS + +See https://github.com/scipy/scipy/issues/16446 for details +on how it fails on macOS and also for proof that it is not +needed because it does not change the wheel size on Linux. + +This was originally discussed in gh-27. It looks like I was +wrong there about the need for stripping. That info was based +on my experience with `numpy.distutils` builds; there it is +needed because of the hardcoded debug flags that cannot be +switched with a flag like `-Ddebug=false` like we use in +`meson-python`. +--- + mesonpy/__init__.py | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py +index 6594812..0896e34 100644 +--- a/mesonpy/__init__.py ++++ b/mesonpy/__init__.py +@@ -487,7 +487,6 @@ def _configure(self, reconfigure: bool = False) -> None: + f'--native-file={os.fspath(self._meson_native_file)}', + # TODO: Allow configuring these arguments + '-Ddebug=false', +- '-Dstrip=true', + '-Doptimization=2', + *setup_args, + ) From ddc40a893c71997d4817c14af3e15da789c268fd Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 3 Jul 2022 12:02:51 -0700 Subject: [PATCH 121/414] build/pkgs/scipy/spkg-install.in: Back to sdh_pip_install --- build/pkgs/scipy/spkg-install.in | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/build/pkgs/scipy/spkg-install.in b/build/pkgs/scipy/spkg-install.in index 6b2df258d1b..37c4e972358 100644 --- a/build/pkgs/scipy/spkg-install.in +++ b/build/pkgs/scipy/spkg-install.in @@ -7,12 +7,4 @@ cd src/ # even when --no-isolation (--no-build-isolation) is in used. We patch it out. sed -i.bak '/build-system/,/project/s/^ *"numpy.*/ "numpy",/' pyproject.toml -# Install: "pip wheel" fails for this package with -# AttributeError: module 'mesonpy' has no attribute 'prepare_metadata_for_build_wheel'. -# We can use "build" instead. -# (However, according to https://github.com/FFY00/meson-python/issues/55 this is just a -# consequence of an earlier error.) -python3 -m build --wheel --no-isolation . -sdh_store_and_pip_install_wheel . - -#sdh_pip_install --no-build-isolation . +sdh_pip_install --no-build-isolation . From fd0eb69806454d30276b8d99ca88ea617eeaf96b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 8 Jul 2022 19:39:13 -0700 Subject: [PATCH 122/414] build/pkgs/scipy: Use 1.9.0rc2 --- build/pkgs/scipy/checksums.ini | 6 +++--- build/pkgs/scipy/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/scipy/checksums.ini b/build/pkgs/scipy/checksums.ini index 11730409e42..66188de7d3c 100644 --- a/build/pkgs/scipy/checksums.ini +++ b/build/pkgs/scipy/checksums.ini @@ -1,5 +1,5 @@ tarball=scipy-VERSION.tar.gz -sha1=f59a225dc4e41f19e0c69bcb4722163f08eb7ce1 -md5=3fe21eea99de291a468c64d218dae0a4 -cksum=2309164171 +sha1=9e4ea6e219a7141ce510651be2a8e63e6ea4f025 +md5=fed06fd35282e493d02585359e2e5541 +cksum=3088600366 upstream_url=https://pypi.io/packages/source/s/scipy/scipy-VERSION.tar.gz diff --git a/build/pkgs/scipy/package-version.txt b/build/pkgs/scipy/package-version.txt index 6ed8c32eb58..65194bf5ebe 100644 --- a/build/pkgs/scipy/package-version.txt +++ b/build/pkgs/scipy/package-version.txt @@ -1 +1 @@ -1.9.0rc1 +1.9.0rc2 From f517441bcf7ae32afd9be3ac3f56a81038e2d374 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Jul 2022 11:39:03 -0700 Subject: [PATCH 123/414] build/pkgs/meson_python: Update to 0.8.0 --- build/pkgs/meson_python/checksums.ini | 6 +++--- build/pkgs/meson_python/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/meson_python/checksums.ini b/build/pkgs/meson_python/checksums.ini index e0c1b8ca4cc..4c1a13cb3f3 100644 --- a/build/pkgs/meson_python/checksums.ini +++ b/build/pkgs/meson_python/checksums.ini @@ -1,5 +1,5 @@ tarball=meson_python-VERSION.tar.gz -sha1=5472571439c7e40b15fa3cc35913db4e01a34ef7 -md5=409ded258744d5251349d729e5a29047 -cksum=3031327791 +sha1=87bc586a5fb4fd25af77c33312357ec421a83711 +md5=71c6dbcaf2b193e3c4d56be3df571b14 +cksum=2230438557 upstream_url=https://pypi.io/packages/source/m/meson_python/meson_python-VERSION.tar.gz diff --git a/build/pkgs/meson_python/package-version.txt b/build/pkgs/meson_python/package-version.txt index 61d13b8d467..a3df0a6959e 100644 --- a/build/pkgs/meson_python/package-version.txt +++ b/build/pkgs/meson_python/package-version.txt @@ -1 +1 @@ -0.6.0.p1 +0.8.0 From ee75343d4548f23ca6d97a8a95854526a49945e4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Jul 2022 11:40:26 -0700 Subject: [PATCH 124/414] build/pkgs/scipy: Update to 1.9.0rc3 --- build/pkgs/scipy/checksums.ini | 6 +++--- build/pkgs/scipy/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/scipy/checksums.ini b/build/pkgs/scipy/checksums.ini index 66188de7d3c..ff78ec3a4e8 100644 --- a/build/pkgs/scipy/checksums.ini +++ b/build/pkgs/scipy/checksums.ini @@ -1,5 +1,5 @@ tarball=scipy-VERSION.tar.gz -sha1=9e4ea6e219a7141ce510651be2a8e63e6ea4f025 -md5=fed06fd35282e493d02585359e2e5541 -cksum=3088600366 +sha1=e1c790ad1d98990925f2bd623566c94f55d86c8d +md5=8f838833943af35aac2d90d9b94e5b34 +cksum=2905963215 upstream_url=https://pypi.io/packages/source/s/scipy/scipy-VERSION.tar.gz diff --git a/build/pkgs/scipy/package-version.txt b/build/pkgs/scipy/package-version.txt index 65194bf5ebe..806b16b355d 100644 --- a/build/pkgs/scipy/package-version.txt +++ b/build/pkgs/scipy/package-version.txt @@ -1 +1 @@ -1.9.0rc2 +1.9.0rc3 From bfd5ff7e02729f4389595f006d96e0367a57b049 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Jul 2022 11:44:05 -0700 Subject: [PATCH 125/414] build/pkgs/meson_python/patches/90.patch: Remove (upstreamed) --- build/pkgs/meson_python/patches/90.patch | 31 ------------------------ 1 file changed, 31 deletions(-) delete mode 100644 build/pkgs/meson_python/patches/90.patch diff --git a/build/pkgs/meson_python/patches/90.patch b/build/pkgs/meson_python/patches/90.patch deleted file mode 100644 index 5f51ea9aa92..00000000000 --- a/build/pkgs/meson_python/patches/90.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 055d7b88b815106a2281320668f218c012eda1ad Mon Sep 17 00:00:00 2001 -From: Ralf Gommers -Date: Sun, 3 Jul 2022 13:34:13 +0200 -Subject: [PATCH] BUG: remove `-Dstrip=true` default, it doesn't work on macOS - -See https://github.com/scipy/scipy/issues/16446 for details -on how it fails on macOS and also for proof that it is not -needed because it does not change the wheel size on Linux. - -This was originally discussed in gh-27. It looks like I was -wrong there about the need for stripping. That info was based -on my experience with `numpy.distutils` builds; there it is -needed because of the hardcoded debug flags that cannot be -switched with a flag like `-Ddebug=false` like we use in -`meson-python`. ---- - mesonpy/__init__.py | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py -index 6594812..0896e34 100644 ---- a/mesonpy/__init__.py -+++ b/mesonpy/__init__.py -@@ -487,7 +487,6 @@ def _configure(self, reconfigure: bool = False) -> None: - f'--native-file={os.fspath(self._meson_native_file)}', - # TODO: Allow configuring these arguments - '-Ddebug=false', -- '-Dstrip=true', - '-Doptimization=2', - *setup_args, - ) From 30c2c7916705146452987d115a010f673bb9ae73 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Jul 2022 16:52:41 -0700 Subject: [PATCH 126/414] build/pkgs/patchelf: New (scipy/meson_py dependency) --- build/pkgs/meson_python/dependencies | 5 ++++- build/pkgs/patchelf/SPKG.rst | 18 ++++++++++++++++++ build/pkgs/patchelf/checksums.ini | 5 +++++ build/pkgs/patchelf/dependencies | 4 ++++ build/pkgs/patchelf/install-requires.txt | 1 + build/pkgs/patchelf/package-version.txt | 1 + build/pkgs/patchelf/spkg-install.in | 4 ++++ build/pkgs/patchelf/type | 1 + 8 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/patchelf/SPKG.rst create mode 100644 build/pkgs/patchelf/checksums.ini create mode 100644 build/pkgs/patchelf/dependencies create mode 100644 build/pkgs/patchelf/install-requires.txt create mode 100644 build/pkgs/patchelf/package-version.txt create mode 100644 build/pkgs/patchelf/spkg-install.in create mode 100644 build/pkgs/patchelf/type diff --git a/build/pkgs/meson_python/dependencies b/build/pkgs/meson_python/dependencies index 10426eb880a..faf5360b3a5 100644 --- a/build/pkgs/meson_python/dependencies +++ b/build/pkgs/meson_python/dependencies @@ -1,7 +1,10 @@ -$(PYTHON) meson pyproject_metadata tomli ninja_build | $(PYTHON_TOOLCHAIN) +$(PYTHON) meson pyproject_metadata tomli ninja_build buildelf | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. meson_python actually declares a dependency on ninja, the Python distribution package. But it only needs the ninja executable. + +buildelf is needed by projects that use meson_python as their build system +for wheel building. diff --git a/build/pkgs/patchelf/SPKG.rst b/build/pkgs/patchelf/SPKG.rst new file mode 100644 index 00000000000..2c9861192e1 --- /dev/null +++ b/build/pkgs/patchelf/SPKG.rst @@ -0,0 +1,18 @@ +patchelf: A small utility to modify the dynamic linker and RPATH of ELF executables. +==================================================================================== + +Description +----------- + +A small utility to modify the dynamic linker and RPATH of ELF executables. + +License +------- + +GPL-3.0-or-later + +Upstream Contact +---------------- + +https://pypi.org/project/patchelf/ + diff --git a/build/pkgs/patchelf/checksums.ini b/build/pkgs/patchelf/checksums.ini new file mode 100644 index 00000000000..c67e77272d7 --- /dev/null +++ b/build/pkgs/patchelf/checksums.ini @@ -0,0 +1,5 @@ +tarball=patchelf-VERSION.tar.gz +sha1=5ee564ad25632ac44313e29d2cc2aec80f3b2556 +md5=4b90a7a8660289538473b7896229642d +cksum=1799259701 +upstream_url=https://pypi.io/packages/source/p/patchelf/patchelf-VERSION.tar.gz diff --git a/build/pkgs/patchelf/dependencies b/build/pkgs/patchelf/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/patchelf/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/patchelf/install-requires.txt b/build/pkgs/patchelf/install-requires.txt new file mode 100644 index 00000000000..fca4680084f --- /dev/null +++ b/build/pkgs/patchelf/install-requires.txt @@ -0,0 +1 @@ +patchelf diff --git a/build/pkgs/patchelf/package-version.txt b/build/pkgs/patchelf/package-version.txt new file mode 100644 index 00000000000..202f94b31ec --- /dev/null +++ b/build/pkgs/patchelf/package-version.txt @@ -0,0 +1 @@ +0.15.0.0 diff --git a/build/pkgs/patchelf/spkg-install.in b/build/pkgs/patchelf/spkg-install.in new file mode 100644 index 00000000000..cde9ca2d6ba --- /dev/null +++ b/build/pkgs/patchelf/spkg-install.in @@ -0,0 +1,4 @@ +cd src +if [ "$UNAME" = "Linux" ]; then + sdh_pip_install . +fi diff --git a/build/pkgs/patchelf/type b/build/pkgs/patchelf/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/patchelf/type @@ -0,0 +1 @@ +standard From 66d989a1096f3e119f1f2be67f95cc2cf7563a57 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Jul 2022 17:15:29 -0700 Subject: [PATCH 127/414] build/pkgs/meson_python/dependencies: fixup --- build/pkgs/meson_python/dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/meson_python/dependencies b/build/pkgs/meson_python/dependencies index faf5360b3a5..160adbf36c9 100644 --- a/build/pkgs/meson_python/dependencies +++ b/build/pkgs/meson_python/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) meson pyproject_metadata tomli ninja_build buildelf | $(PYTHON_TOOLCHAIN) +$(PYTHON) meson pyproject_metadata tomli ninja_build patchelf | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. From b8e8ad71e0a560db6538b49daff35a8b5809c78a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 26 Jul 2022 18:17:30 -0700 Subject: [PATCH 128/414] build/pkgs/patchelf: Switch to using non-Python package --- build/pkgs/patchelf/SPKG.rst | 7 +++---- build/pkgs/patchelf/checksums.ini | 10 +++++----- build/pkgs/patchelf/dependencies | 2 +- build/pkgs/patchelf/install-requires.txt | 1 - build/pkgs/patchelf/package-version.txt | 2 +- build/pkgs/patchelf/spkg-install.in | 4 +++- 6 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 build/pkgs/patchelf/install-requires.txt diff --git a/build/pkgs/patchelf/SPKG.rst b/build/pkgs/patchelf/SPKG.rst index 2c9861192e1..a2d1a1c4ccf 100644 --- a/build/pkgs/patchelf/SPKG.rst +++ b/build/pkgs/patchelf/SPKG.rst @@ -1,5 +1,5 @@ -patchelf: A small utility to modify the dynamic linker and RPATH of ELF executables. -==================================================================================== +patchelf: A small utility to modify the dynamic linker and RPATH of ELF executables +=================================================================================== Description ----------- @@ -14,5 +14,4 @@ GPL-3.0-or-later Upstream Contact ---------------- -https://pypi.org/project/patchelf/ - +https://github.com/NixOS/patchelf diff --git a/build/pkgs/patchelf/checksums.ini b/build/pkgs/patchelf/checksums.ini index c67e77272d7..728e73fbccd 100644 --- a/build/pkgs/patchelf/checksums.ini +++ b/build/pkgs/patchelf/checksums.ini @@ -1,5 +1,5 @@ -tarball=patchelf-VERSION.tar.gz -sha1=5ee564ad25632ac44313e29d2cc2aec80f3b2556 -md5=4b90a7a8660289538473b7896229642d -cksum=1799259701 -upstream_url=https://pypi.io/packages/source/p/patchelf/patchelf-VERSION.tar.gz +tarball=patchelf-VERSION.tar.bz2 +sha1=b116615abd01d5093a6fe674cb4629c2be38cce9 +md5=0a707393c1b95291b19b9dd06a4dd546 +cksum=3263104798 +upstream_url=https://github.com/NixOS/patchelf/releases/download/VERSION/patchelf-VERSION.tar.bz2 diff --git a/build/pkgs/patchelf/dependencies b/build/pkgs/patchelf/dependencies index 0738c2d7777..c856a61a50b 100644 --- a/build/pkgs/patchelf/dependencies +++ b/build/pkgs/patchelf/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) +| bzip2 ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/patchelf/install-requires.txt b/build/pkgs/patchelf/install-requires.txt deleted file mode 100644 index fca4680084f..00000000000 --- a/build/pkgs/patchelf/install-requires.txt +++ /dev/null @@ -1 +0,0 @@ -patchelf diff --git a/build/pkgs/patchelf/package-version.txt b/build/pkgs/patchelf/package-version.txt index 202f94b31ec..a5510516948 100644 --- a/build/pkgs/patchelf/package-version.txt +++ b/build/pkgs/patchelf/package-version.txt @@ -1 +1 @@ -0.15.0.0 +0.15.0 diff --git a/build/pkgs/patchelf/spkg-install.in b/build/pkgs/patchelf/spkg-install.in index cde9ca2d6ba..b4a009197cd 100644 --- a/build/pkgs/patchelf/spkg-install.in +++ b/build/pkgs/patchelf/spkg-install.in @@ -1,4 +1,6 @@ cd src if [ "$UNAME" = "Linux" ]; then - sdh_pip_install . + sdh_configure + sdh_make + sdh_make_install fi From 270789e0b783792057c80634091ced4645e30dbb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Jul 2022 09:44:34 -0700 Subject: [PATCH 129/414] build/pkgs/patchelf: Downgrade to 0.13.1 --- build/pkgs/patchelf/checksums.ini | 6 +++--- build/pkgs/patchelf/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/patchelf/checksums.ini b/build/pkgs/patchelf/checksums.ini index 728e73fbccd..bd52a82dbc5 100644 --- a/build/pkgs/patchelf/checksums.ini +++ b/build/pkgs/patchelf/checksums.ini @@ -1,5 +1,5 @@ tarball=patchelf-VERSION.tar.bz2 -sha1=b116615abd01d5093a6fe674cb4629c2be38cce9 -md5=0a707393c1b95291b19b9dd06a4dd546 -cksum=3263104798 +sha1=5d9c1690c0fbe70c312f43d597e04b6c1eeffc60 +md5=04d243d3626a33201b0d6eef0e2c4317 +cksum=92812155 upstream_url=https://github.com/NixOS/patchelf/releases/download/VERSION/patchelf-VERSION.tar.bz2 diff --git a/build/pkgs/patchelf/package-version.txt b/build/pkgs/patchelf/package-version.txt index a5510516948..c317a91891f 100644 --- a/build/pkgs/patchelf/package-version.txt +++ b/build/pkgs/patchelf/package-version.txt @@ -1 +1 @@ -0.15.0 +0.13.1 From 1c53962dd6cfa678d4d809ceaa2288c224fa6b35 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Jul 2022 15:20:43 -0700 Subject: [PATCH 130/414] build/pkgs/ninja_build: Update to 1.11.0 --- build/pkgs/ninja_build/checksums.ini | 7 ++++--- build/pkgs/ninja_build/package-version.txt | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/pkgs/ninja_build/checksums.ini b/build/pkgs/ninja_build/checksums.ini index cd1733990c7..d3914da794a 100644 --- a/build/pkgs/ninja_build/checksums.ini +++ b/build/pkgs/ninja_build/checksums.ini @@ -1,4 +1,5 @@ tarball=ninja_build-VERSION.tar.gz -sha1=17219deb34dd816363e37470f77ff7231509143a -md5=5fdb04461cc7f5d02536b3bfc0300166 -cksum=28253504 +sha1=f8c9279bdd4efc63b1a6be3b8c5a5031699af9ac +md5=7d1a1a2f5cdc06795b3054df5c17d5ef +cksum=3142198237 +upstream_url=https://github.com/ninja-build/ninja/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/ninja_build/package-version.txt b/build/pkgs/ninja_build/package-version.txt index 53adb84c822..1cac385c6cb 100644 --- a/build/pkgs/ninja_build/package-version.txt +++ b/build/pkgs/ninja_build/package-version.txt @@ -1 +1 @@ -1.8.2 +1.11.0 From 4fb3a63847487c0b205a4a02eed24a069f84513c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Jul 2022 15:24:16 -0700 Subject: [PATCH 131/414] build/pkgs/ninja_build/spkg-configure.m4: Set lower version bound to 1.8.2 --- build/pkgs/ninja_build/spkg-configure.m4 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build/pkgs/ninja_build/spkg-configure.m4 b/build/pkgs/ninja_build/spkg-configure.m4 index d9661574a13..fb6c4f00985 100644 --- a/build/pkgs/ninja_build/spkg-configure.m4 +++ b/build/pkgs/ninja_build/spkg-configure.m4 @@ -1,11 +1,12 @@ SAGE_SPKG_CONFIGURE( [ninja_build], [ - AC_CACHE_CHECK([for ninja >= 1.7.2], [ac_cv_path_NINJA], [ + dnl meson_python needs 1.8.2 or later + AC_CACHE_CHECK([for ninja >= 1.8.2], [ac_cv_path_NINJA], [ AC_PATH_PROGS_FEATURE_CHECK([NINJA], [ninja], [ ninja_version=`$ac_path_NINJA --version 2>&1 \ | $SED -n -e 's/\([[0-9]]*\.[[0-9]]*\.[[0-9]]*\).*/\1/p'` AS_IF([test -n "$ninja_version"], [ - AX_COMPARE_VERSION([$ninja_version], [ge], [1.7.2], [ + AX_COMPARE_VERSION([$ninja_version], [ge], [1.8.2], [ ac_cv_path_NINJA="$ac_path_NINJA" ac_path_NINJA_found=: ]) From a4c382153030b8ef99eb2e35e0a5a493f38ba73b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 2 Aug 2022 16:30:37 -0700 Subject: [PATCH 132/414] build/pkgs/meson_python/patches: Add https://github.com/FFY00/meson-python/pull/126 --- build/pkgs/meson_python/patches/126.patch | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 build/pkgs/meson_python/patches/126.patch diff --git a/build/pkgs/meson_python/patches/126.patch b/build/pkgs/meson_python/patches/126.patch new file mode 100644 index 00000000000..3f417b41a5e --- /dev/null +++ b/build/pkgs/meson_python/patches/126.patch @@ -0,0 +1,26 @@ +From 320ecea8996a062317ac7790eebe17f5f3f95551 Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Tue, 2 Aug 2022 16:20:50 -0700 +Subject: [PATCH] Project.platform_tag: Handle the case of 32bit Python on + x86_64 + +--- + mesonpy/__init__.py | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py +index aff3dd1..bdcda25 100644 +--- a/mesonpy/__init__.py ++++ b/mesonpy/__init__.py +@@ -684,6 +684,11 @@ def platform_tag(self) -> str: + # https://github.com/pypa/packaging/issues/578 + parts[1] += '.0' + platform_ = '-'.join(parts) ++ elif parts[0] == 'linux' and parts[1] == 'x86_64' and sys.maxsize == 2147483647: ++ # 32-bit Python running on an x86_64 host ++ # https://github.com/FFY00/meson-python/issues/123 ++ parts[1] = 'i686' ++ platform_ = '-'.join(parts) + return platform_.replace('-', '_').replace('.', '_') + + def _calculate_file_abi_tag_heuristic_windows(self, filename: str) -> Optional[mesonpy._tags.Tag]: From 12698ea05dcf048574dbac4650900f8dc77a775f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 3 Aug 2022 23:27:42 -0700 Subject: [PATCH 133/414] build/pkgs/scipy: Update to 1.9.0 --- build/pkgs/scipy/checksums.ini | 6 +++--- build/pkgs/scipy/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/scipy/checksums.ini b/build/pkgs/scipy/checksums.ini index ff78ec3a4e8..8090f084328 100644 --- a/build/pkgs/scipy/checksums.ini +++ b/build/pkgs/scipy/checksums.ini @@ -1,5 +1,5 @@ tarball=scipy-VERSION.tar.gz -sha1=e1c790ad1d98990925f2bd623566c94f55d86c8d -md5=8f838833943af35aac2d90d9b94e5b34 -cksum=2905963215 +sha1=22c0e73b933b938c272c6eb747cd6b70ab2f9b83 +md5=1f2e527930ddfa15a622b146dae42144 +cksum=994041683 upstream_url=https://pypi.io/packages/source/s/scipy/scipy-VERSION.tar.gz diff --git a/build/pkgs/scipy/package-version.txt b/build/pkgs/scipy/package-version.txt index 806b16b355d..f8e233b2733 100644 --- a/build/pkgs/scipy/package-version.txt +++ b/build/pkgs/scipy/package-version.txt @@ -1 +1 @@ -1.9.0rc3 +1.9.0 From f896ae19d78b17476dbe2079251832fa3b4f21cb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 3 Aug 2022 23:37:49 -0700 Subject: [PATCH 134/414] build/pkgs/meson_python: Update to 0.8.1 --- build/pkgs/meson_python/checksums.ini | 6 +++--- build/pkgs/meson_python/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/meson_python/checksums.ini b/build/pkgs/meson_python/checksums.ini index 4c1a13cb3f3..e72f2a738cc 100644 --- a/build/pkgs/meson_python/checksums.ini +++ b/build/pkgs/meson_python/checksums.ini @@ -1,5 +1,5 @@ tarball=meson_python-VERSION.tar.gz -sha1=87bc586a5fb4fd25af77c33312357ec421a83711 -md5=71c6dbcaf2b193e3c4d56be3df571b14 -cksum=2230438557 +sha1=18c1742379f1a9f3905c67c348aeb2442b02c119 +md5=38cc212d532a55ba4a53c572656ecd70 +cksum=4204960713 upstream_url=https://pypi.io/packages/source/m/meson_python/meson_python-VERSION.tar.gz diff --git a/build/pkgs/meson_python/package-version.txt b/build/pkgs/meson_python/package-version.txt index a3df0a6959e..6f4eebdf6f6 100644 --- a/build/pkgs/meson_python/package-version.txt +++ b/build/pkgs/meson_python/package-version.txt @@ -1 +1 @@ -0.8.0 +0.8.1 From 9e64ba4905de7d5548e175dc6ee8a5f3a541a9aa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 4 Aug 2022 00:14:54 -0700 Subject: [PATCH 135/414] build/pkgs/meson: Update to 0.63.0 --- build/pkgs/meson/checksums.ini | 6 +++--- build/pkgs/meson/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/meson/checksums.ini b/build/pkgs/meson/checksums.ini index e3b435cc812..c46bd7bb761 100644 --- a/build/pkgs/meson/checksums.ini +++ b/build/pkgs/meson/checksums.ini @@ -1,5 +1,5 @@ tarball=meson-VERSION.tar.gz -sha1=25a82635f5b4e43d3b126ecaaccc99bfff575a92 -md5=e6521b43730901bdd67afa2adefa64a3 -cksum=1062989682 +sha1=85b1c71b598686e40632b5a57ac7f2bdd1dcd926 +md5=d9e7d69f73f875004fbb3cc8bfe2a39b +cksum=339088378 upstream_url=https://pypi.io/packages/source/m/meson/meson-VERSION.tar.gz diff --git a/build/pkgs/meson/package-version.txt b/build/pkgs/meson/package-version.txt index 92c648bd8c3..70cd2261d5c 100644 --- a/build/pkgs/meson/package-version.txt +++ b/build/pkgs/meson/package-version.txt @@ -1 +1 @@ -0.62.2 +0.63.0 From 719fed550269b8c138f1cb323d6c8f31c8862fed Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 4 Aug 2022 19:27:56 -0700 Subject: [PATCH 136/414] fusion_ring r_matrix zero bugfix --- src/sage/algebras/fusion_rings/fusion_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index 75b28678c15..e4a52b4e35a 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -957,7 +957,7 @@ def r_matrix(self, i, j, k, base_coercion=True): True """ if self.Nk_ij(i, j, k) == 0: - return self.field().zero() + return self.field().zero() if (not base_coercion) or (self._basecoer is None) else self.fvars_field().zero() if i != j: ret = self.root_of_unity((k.twist(reduced=False) - i.twist(reduced=False) - j.twist(reduced=False)) / 2, base_coercion=False) else: From aa7ba6cedb8ae6620a5ef6f867f04dee26a13700 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 10 Aug 2022 16:12:24 +0900 Subject: [PATCH 137/414] Reviewer changes and doc fixes. --- src/sage/algebras/all.py | 1 + src/sage/algebras/fusion_rings/all.py | 2 +- src/sage/algebras/fusion_rings/f_matrix.py | 597 +++++++++--------- .../fast_parallel_fmats_methods.pxd | 1 + .../fast_parallel_fusion_ring_braid_repn.pxd | 1 + .../fast_parallel_fusion_ring_braid_repn.pyx | 8 +- .../algebras/fusion_rings/poly_tup_engine.pyx | 4 +- .../algebras/fusion_rings/shm_managers.pxd | 2 +- .../algebras/fusion_rings/shm_managers.pyx | 349 +++++----- src/sage/combinat/root_system/all.py | 2 +- 10 files changed, 511 insertions(+), 456 deletions(-) diff --git a/src/sage/algebras/all.py b/src/sage/algebras/all.py index ac06fa76ab8..cd71ece02d8 100644 --- a/src/sage/algebras/all.py +++ b/src/sage/algebras/all.py @@ -65,3 +65,4 @@ lazy_import('sage.algebras.cluster_algebra', 'ClusterAlgebra') lazy_import('sage.algebras.yangian', 'Yangian') + diff --git a/src/sage/algebras/fusion_rings/all.py b/src/sage/algebras/fusion_rings/all.py index 6742afd5d55..041ced4f28d 100644 --- a/src/sage/algebras/fusion_rings/all.py +++ b/src/sage/algebras/fusion_rings/all.py @@ -3,7 +3,7 @@ """ # **************************************************************************** -# Copyright (C) 2021 Travis Scrimshaw +# Copyright (C) 2022 Guillermo Aboumrad # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 49e814f20f9..3c24c942dd0 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -1,5 +1,5 @@ r""" -F-Matrix Factory for FusionRings +F-Matries of fusion rings """ # **************************************************************************** # Copyright (C) 2019 Daniel Bump @@ -43,239 +43,244 @@ from sage.matrix.constructor import matrix from sage.misc.misc import get_main_globals from sage.rings.ideal import Ideal -from sage.rings.polynomial.all import PolynomialRing +from sage.structure.sage_object import SageObject +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.polydict import ETuple from sage.rings.qqbar import AA, QQbar, number_field_elements_from_algebraics -class FMatrix(): +class FMatrix(SageObject): + r""" + Return an F-Matrix factory for a :class:`FusionRing`. + + INPUT: + + - ``FR`` -- a :class:`FusionRing` + - ``fusion_label`` -- (optional) a string used to label basis elements + of the :class:`FusionRing` associated to ``self`` + (see :meth:`FusionRing.fusion_labels`) + - ``var_prefix`` -- (optional) a string indicating the desired prefix + for variables denoting F-symbols to be solved + - ``inject_variables`` -- (default: ``False``) a boolean indicating + whether to inject variables (:class:`FusionRing` basis element + labels and F-symbols) into the global namespace + + The :class:`FusionRing` or Verlinde algebra is the + Grothendieck ring of a modular tensor category [BaKi2001]_. + Such categories arise in conformal field theory or in the + representation theories of affine Lie algebras, or + quantum groups at roots of unity. They have applications + to low dimensional topology and knot theory, to conformal + field theory and to topological quantum computing. The + :class:`FusionRing` captures much information about a fusion + category, but to complete the picture, the F-matrices or + 6j-symbols are needed. For example these are required in + order to construct braid group representations. This + can be done using the :class:`FusionRing` method + :meth:`FusionRing.get_braid_generators`, which uses + the F-matrix. + + We only undertake to compute the F-matrix if the + :class:`FusionRing` is *multiplicity free* meaning that + the Fusion coefficients `N^{ij}_k` are bounded + by 1. For Cartan Types `X_r` and level `k`, + the multiplicity-free cases are given by the + following table. + + +------------------------+----------+ + | Cartan Type | `k` | + +========================+==========+ + | `A_1` | any | + +------------------------+----------+ + | `A_r, r\geq 2` | `\leq 2` | + +------------------------+----------+ + | `B_r, r\geq 2` | `\leq 2` | + +------------------------+----------+ + | `C_2` | `\leq 2` | + +------------------------+----------+ + | `C_r, r\geq 3` | `\leq 1` | + +------------------------+----------+ + | `D_r, r\geq 4` | `\leq 2` | + +------------------------+----------+ + | `G_2,F_4,E_6,E_7` | `\leq 2` | + +------------------------+----------+ + | `E_8` | `\leq 3` | + +------------------------+----------+ + + Beyond this limitation, computation of the F-matrix + can involve very large systems of equations. A + rule of thumb is that this code can compute the + F-matrix for systems with `\leq 14` simple objects + (primary fields) on a machine with 16 GB of memory. + (Larger examples can be quite time consuming.) + + The :class:`FusionRing` and its methods capture much + of the structure of the underlying tensor category. + But an important aspect that is not encoded in the + fusion ring is the associator, which is a homomorphism + `(A\otimes B)\otimes C\to A\otimes(B\otimes C)` that + requires an additional tool, the F-matrix or 6j-symbol. + To specify this, we fix a simple object `D` + and represent the transformation + + .. MATH:: + + \text{Hom}(D,(A\otimes B)\otimes C) + \to \text{Hom}(D,A\otimes(B\otimes C)) + + by a matrix `F^{ABC}_D`. This depends on a pair of + additional simple objects `X` and `Y`. Indeed, we can + get a basis for `\text{Hom}(D,(A\otimes B)\otimes C)` + indexed by simple objects `X` in which the corresponding + homomorphism factors through `X\otimes C`, and similarly + `\text{Hom}(D,A\otimes(B\otimes C))` has a basis indexed + by `Y`, in which the basis vector factors through `A\otimes Y`. + + See [TTWL2009]_ for an introduction to this topic, + [EGNO2015]_ Section 4.9 for a precise mathematical + definition, and [Bond2007]_ Section 2.5 for a discussion + of how to compute the F-matrix. In addition to + [Bond2007]_, worked out F-matrices may be found in + [RoStWa2009]_ and [CHW2015]_. + + The F-matrix is only determined up to a *gauge*. This + is a family of embeddings `C \to A\otimes B` for + simple objects `A,B,C` such that `\text{Hom}(C, A\otimes B)` + is nonzero. Changing the gauge changes the F-matrix though + not in a very essential way. By varying the gauge it is + possible to make the F-matrices unitary, or it is possible + to make them cyclotomic. + + Due to the large number of equations we may fail to find a + Groebner basis if there are too many variables. + + EXAMPLES:: + + sage: I = FusionRing("E8", 2, conjugate=True) + sage: I.fusion_labels(["i0","p","s"],inject_variables=True) + sage: f = FMatrix(I,inject_variables=True); f + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients + + We have injected two sets of variables to the global namespace. + We created three variables ``i0, p, s`` to represent the + primary fields (simple elements) of the :class:`FusionRing`. Creating + the :class:`FMatrix` factory also created variables + ``fx1, fx2, ..., fx14`` in order to solve the hexagon and pentagon + equations describing the F-matrix. Since we called :class:`FMatrix` + with the parameter ``inject_variables=True``, these have been injected + into the global namespace. This is not necessary for the code to work + but if you want to run the code experimentally you may want access + to these variables. + + EXAMPLES:: + + sage: f.fmatrix(s,s,s,s) + [fx10 fx11] + [fx12 fx13] + + The F-matrix has not been computed at this stage, so + the F-matrix `F^{sss}_s` is filled with variables + ``fx10``, ``fx11``, ``fx12``, ``fx13``. The task is + to solve for these. + + As explained above The F-matrix `(F^{ABC}_D)_{X,Y}` + two other variables `X` and `Y`. We have methods to + tell us (depending on `A,B,C,D`) what the possibilities + for these are. In this example with `A=B=C=D=s` + both `X` and `Y` are allowed to be `i_0` or `s`. + + :: + + sage: f.f_from(s,s,s,s), f.f_to(s,s,s,s) + ([i0, p], [i0, p]) + + The last two statments show that the possible values of + `X` and `Y` when `A = B = C = D = s` are `i_0` and `p`. + + The F-matrix is computed by solving the so-called + pentagon and hexagon equations. The *pentagon equations* + reflect the Mac Lane pentagon axiom in the definition + of a monoidal category. The hexagon relations + reflect the axioms of a *braided monoidal category*, + which are constraints on both the F-matrix and on + the R-matrix. Optionally, orthogonality constraints + may be imposed to obtain an orthogonal F-matrix. + + :: + + sage: sorted(f.get_defining_equations("pentagons"))[1:3] + [fx9*fx12 - fx2*fx13, fx4*fx11 - fx2*fx13] + sage: sorted(f.get_defining_equations("hexagons"))[1:3] + [fx6 - 1, fx2 + 1] + sage: sorted(f.get_orthogonality_constraints())[1:3] + [fx10*fx11 + fx12*fx13, fx10*fx11 + fx12*fx13] + + There are two methods available to compute an F-matrix. + The first, :meth:`find_cyclotomic_solution` uses only + the pentagon and hexagon relations. The second, + :meth:`find_orthogonal_solution` uses additionally + the orthogonality relations. There are some differences + that should be kept in mind. + + :meth:`find_cyclotomic_solution` currently works only with + smaller examples. For example the :class:`FusionRing` for `G_2` + at level 2 is too large. When it is available, this method + produces an F-matrix whose entries are in the same + cyclotomic field as the underlying :class:`FusionRing`. + + :: + + sage: f.find_cyclotomic_solution() + Setting up hexagons and pentagons... + Finding a Groebner basis... + Solving... + Fixing the gauge... + adding equation... fx1 - 1 + adding equation... fx11 - 1 + Done! + + We now have access to the values of the F-matrix using + the methods :meth:`fmatrix` and :meth:`fmat`:: + + sage: f.fmatrix(s,s,s,s) + [(-1/2*zeta128^48 + 1/2*zeta128^16) 1] + [ 1/2 (1/2*zeta128^48 - 1/2*zeta128^16)] + sage: f.fmat(s,s,s,s,p,p) + (1/2*zeta128^48 - 1/2*zeta128^16) + + :meth:`find_orthogonal_solution` is much more powerful + and is capable of handling large cases, sometimes + quickly but sometimes (in larger cases) after hours of + computation. Its F-matrices are not always in the + cyclotomic field that is the base ring of the underlying + :class:`FusionRing`, but sometimes in an extension field adjoining + some square roots. When this happens, the :class:`FusionRing` is + modified, adding an attribute ``_basecoer`` that is + a coercion from the cyclotomic field to the field + containing the F-matrix. The field containing the F-matrix + is available through :meth:`field`. + + :: + + sage: f = FMatrix(FusionRing("B3",2)) + sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # not tested (~100 s) + sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # not tested + True + + sage: f = FMatrix(FusionRing("G2",2)) + sage: f.find_orthogonal_solution(verbose=False) # long time (~11 s) + sage: f.field() # long time + Algebraic Field + """ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variables=False): r""" - Return an F-Matrix factory for a :class:`FusionRing`. - - INPUT: - - - ``FR`` -- a :class:`FusionRing` - - - ``fusion_label`` -- (optional) a string used to label basis elements - of the :class:`FusionRing` associated to ``self`` - - See :meth:`FusionRing.fusion_labels` - - - ``var_prefix`` -- (optional) a string indicating the desired prefix - for variables denoting F-symbols to be solved - - - ``inject_variables`` -- (default: ``False``) a boolean indicating - whether to inject variables (:class:`FusionRing` basis element - labels and F-symbols) into the global namespace - - The :class:`FusionRing` or Verlinde algebra is the - Grothendieck ring of a modular tensor category [BaKi2001]_. - Such categories arise in conformal field theory or in the - representation theories of affine Lie algebras, or - quantum groups at roots of unity. They have applications - to low dimensional topology and knot theory, to conformal - field theory and to topological quantum computing. The - :class:`FusionRing` captures much information about a fusion - category, but to complete the picture, the F-matrices or - 6j-symbols are needed. For example these are required in - order to construct braid group representations. This - can be done using the :class:`FusionRing` method - :meth:`FusionRing.get_braid_generators`, which uses - the F-matrix. - - We only undertake to compute the F-matrix if the - :class:`FusionRing` is *multiplicity free* meaning that - the Fusion coefficients `N^{ij}_k` are bounded - by 1. For Cartan Types `X_r` and level `k`, - the multiplicity-free cases are given by the - following table. - - +------------------------+----------+ - | Cartan Type | `k` | - +========================+==========+ - | `A_1` | any | - +------------------------+----------+ - | `A_r, r\geq 2` | `\leq 2` | - +------------------------+----------+ - | `B_r, r\geq 2` | `\leq 2` | - +------------------------+----------+ - | `C_2` | `\leq 2` | - +------------------------+----------+ - | `C_r, r\geq 3` | `\leq 1` | - +------------------------+----------+ - | `D_r, r\geq 4` | `\leq 2` | - +------------------------+----------+ - | `G_2,F_4,E_6,E_7` | `\leq 2` | - +------------------------+----------+ - | `E_8` | `\leq 3` | - +------------------------+----------+ - - Beyond this limitation, computation of the F-matrix - can involve very large systems of equations. A - rule of thumb is that this code can compute the - F-matrix for systems with `\leq 14` simple objects - (primary fields) on a machine with 16 GB of memory. - (Larger examples can be quite time consuming.) - - The :class:`FusionRing` and its methods capture much - of the structure of the underlying tensor category. - But an important aspect that is not encoded in the - fusion ring is the associator, which is a homomorphism - `(A\otimes B)\otimes C\to A\otimes(B\otimes C)` that - requires an additional tool, the F-matrix or 6j-symbol. - To specify this, we fix a simple object `D` - and represent the transformation - - .. MATH:: - - \text{Hom}(D,(A\otimes B)\otimes C) - \to \text{Hom}(D,A\otimes(B\otimes C)) - - by a matrix `F^{ABC}_D`. This depends on a pair of - additional simple objects `X` and `Y`. Indeed, we can - get a basis for `\text{Hom}(D,(A\otimes B)\otimes C)` - indexed by simple objects `X` in which the corresponding - homomorphism factors through `X\otimes C`, and similarly - `\text{Hom}(D,A\otimes(B\otimes C))` has a basis indexed - by `Y`, in which the basis vector factors through `A\otimes Y`. - - See [TTWL2009]_ for an introduction to this topic, - [EGNO2015]_ Section 4.9 for a precise mathematical - definition, and [Bond2007]_ Section 2.5 for a discussion - of how to compute the F-matrix. In addition to - [Bond2007]_, worked out F-matrices may be found in - [RoStWa2009]_ and [CHW2015]_. - - The F-matrix is only determined up to a *gauge*. This - is a family of embeddings `C \to A\otimes B` for - simple objects `A,B,C` such that `\text{Hom}(C, A\otimes B)` - is nonzero. Changing the gauge changes the F-matrix though - not in a very essential way. By varying the gauge it is - possible to make the F-matrices unitary, or it is possible - to make them cyclotomic. - - Due to the large number of equations we may fail to find a - Groebner basis if there are too many variables. + Initialize ``self``. EXAMPLES:: - sage: I = FusionRing("E8",2,conjugate=True) - sage: I.fusion_labels(["i0","p","s"],inject_variables=True) - sage: f = FMatrix(I,inject_variables=True); f - creating variables fx1..fx14 - Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients - - We have injected two sets of variables to the global namespace. - We created three variables ``i0, p, s`` to represent the - primary fields (simple elements) of the :class:`FusionRing`. Creating - the :class:`FMatrix` factory also created variables - ``fx1, fx2, ..., fx14`` in order to solve the hexagon and pentagon - equations describing the F-matrix. Since we called :class:`FMatrix` - with the parameter ``inject_variables=True``, these have been injected - into the global namespace. This is not necessary for the code to work - but if you want to run the code experimentally you may want access - to these variables. - - EXAMPLES:: - - sage: f.fmatrix(s,s,s,s) - [fx10 fx11] - [fx12 fx13] - - The F-matrix has not been computed at this stage, so - the F-matrix `F^{sss}_s` is filled with variables - ``fx10``, ``fx11``, ``fx12``, ``fx13``. The task is - to solve for these. - - As explained above The F-matrix `(F^{ABC}_D)_{X,Y}` - two other variables `X` and `Y`. We have methods to - tell us (depending on `A,B,C,D`) what the possibilities - for these are. In this example with `A=B=C=D=s` - both `X` and `Y` are allowed to be `i_0` or `s`. - - :: - - sage: f.f_from(s,s,s,s), f.f_to(s,s,s,s) - ([i0, p], [i0, p]) - - The last two statments show that the possible values of - `X` and `Y` when `A = B = C = D = s` are `i_0` and `p`. - - The F-matrix is computed by solving the so-called - pentagon and hexagon equations. The *pentagon equations* - reflect the Mac Lane pentagon axiom in the definition - of a monoidal category. The hexagon relations - reflect the axioms of a *braided monoidal category*, - which are constraints on both the F-matrix and on - the R-matrix. Optionally, orthogonality constraints - may be imposed to obtain an orthogonal F-matrix. - - :: - - sage: sorted(f.get_defining_equations("pentagons"))[1:3] - [fx9*fx12 - fx2*fx13, fx4*fx11 - fx2*fx13] - sage: sorted(f.get_defining_equations("hexagons"))[1:3] - [fx6 - 1, fx2 + 1] - sage: sorted(f.get_orthogonality_constraints())[1:3] - [fx10*fx11 + fx12*fx13, fx10*fx11 + fx12*fx13] - - There are two methods available to compute an F-matrix. - The first, :meth:`find_cyclotomic_solution` uses only - the pentagon and hexagon relations. The second, - :meth:`find_orthogonal_solution` uses additionally - the orthogonality relations. There are some differences - that should be kept in mind. - - :meth:`find_cyclotomic_solution` currently works only with - smaller examples. For example the :class:`FusionRing` for `G_2` - at level 2 is too large. When it is available, this method - produces an F-matrix whose entries are in the same - cyclotomic field as the underlying :class:`FusionRing`. - - :: - - sage: f.find_cyclotomic_solution() - Setting up hexagons and pentagons... - Finding a Groebner basis... - Solving... - Fixing the gauge... - adding equation... fx1 - 1 - adding equation... fx11 - 1 - Done! - - We now have access to the values of the F-matrix using - the methods :meth:`fmatrix` and :meth:`fmat`:: - - sage: f.fmatrix(s,s,s,s) - [(-1/2*zeta128^48 + 1/2*zeta128^16) 1] - [ 1/2 (1/2*zeta128^48 - 1/2*zeta128^16)] - sage: f.fmat(s,s,s,s,p,p) - (1/2*zeta128^48 - 1/2*zeta128^16) - - :meth:`find_orthogonal_solution` is much more powerful - and is capable of handling large cases, sometimes - quickly but sometimes (in larger cases) after hours of - computation. Its F-matrices are not always in the - cyclotomic field that is the base ring of the underlying - :class:`FusionRing`, but sometimes in an extension field adjoining - some square roots. When this happens, the :class:`FusionRing` is - modified, adding an attribute ``_basecoer`` that is - a coercion from the cyclotomic field to the field - containing the F-matrix. The field containing the F-matrix - is available through :meth:`field`. - - :: - sage: f = FMatrix(FusionRing("B3",2)) - sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # not tested (~100 s) - sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # not tested - True - - sage: f = FMatrix(FusionRing("G2",2)) - sage: f.find_orthogonal_solution(verbose=False) # long time (~11 s) - sage: f.field() # long time - Algebraic Field + sage: TestSuite(f).run() """ self._FR = fusion_ring if inject_variables and (self._FR._fusion_labels is None): @@ -306,8 +311,10 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab ### Class utilities ### ####################### - def __repr__(self): + def _repr_(self): """ + Return a string representation of ``self``. + EXAMPLES:: sage: FMatrix(FusionRing("B2", 1)) @@ -329,7 +336,7 @@ def clear_equations(self): sage: len(f.ideal_basis) == 0 True """ - self.ideal_basis = list() + self.ideal_basis = [] def clear_vars(self): r""" @@ -350,12 +357,14 @@ def clear_vars(self): fx0 """ self._fvars = {t: self._poly_ring.gen(idx) for idx, t in self._idx_to_sextuple.items()} - self._solved = [False]*self._poly_ring.ngens() + self._solved = [False] * self._poly_ring.ngens() def _reset_solver_state(self): r""" - Reset solver state and clear relevant cache. Used to ensure - state variables are the same for each orthogonal solver run. + Reset solver state and clear relevant cache. + + Used to ensure state variables are the same for each + orthogonal solver run. EXAMPLES:: @@ -380,7 +389,6 @@ def _reset_solver_state(self): True sage: for k, v in f._ks.items(): ....: k - ....: sage: len(f._nnz.nonzero_positions()) == 1 True sage: all(len(x.q_dimension.cache) == 0 for x in f.FR().basis()) @@ -392,14 +400,14 @@ def _reset_solver_state(self): """ self._FR._basecoer = None self._field = self._FR.field() - self._non_cyc_roots = list() + self._non_cyc_roots = [] self._poly_ring = self._poly_ring.change_ring(self._field) self._chkpt_status = -1 self.clear_vars() self.clear_equations() n = self._poly_ring.ngens() - self._var_degs = [0]*n - self._kp = dict() + self._var_degs = [0] * n + self._kp = {} self._ks = KSHandler(n,self._field) self._singles = self.get_fvars_by_size(1,indices=True) self._nnz = self._get_known_nonz() @@ -436,7 +444,8 @@ def fmat(self, a, b, c, d, x, y, data=True): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), (zeta60^14 - zeta60^6 - zeta60^4 + 1)] """ - if self._FR.Nk_ij(a,b,x) == 0 or self._FR.Nk_ij(x,c,d) == 0 or self._FR.Nk_ij(b,c,y) == 0 or self._FR.Nk_ij(a,y,d) == 0: + if (self._FR.Nk_ij(a,b,x) == 0 or self._FR.Nk_ij(x,c,d) == 0 + or self._FR.Nk_ij(b,c,y) == 0 or self._FR.Nk_ij(a,y,d) == 0): return 0 #Some known zero F-symbols @@ -471,7 +480,7 @@ def fmatrix(self,a,b,c,d): INPUT: - - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing` + - ``a, b, c, d`` -- basis elements of the associated :class:`FusionRing` EXAMPLES:: @@ -601,11 +610,11 @@ def findcases(self,output=False): def f_from(self,a,b,c,d): r""" Return the possible `x` such that there are morphisms - `d \to x \otimes c \to (a\otimes b)\otimes c`. + `d \to x \otimes c \to (a \otimes b) \otimes c`. INPUT: - - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing` + - ``a, b, c, d`` -- basis elements of the associated :class:`FusionRing` EXAMPLES:: @@ -625,11 +634,11 @@ def f_from(self,a,b,c,d): def f_to(self,a,b,c,d): r""" Return the possible `y` such that there are morphisms - `d\to a\otimes y\to a\otimes(b\otimes c)`. + `d \to a \otimes y \to a \otimes (b \otimes c)`. INPUT: - - ``a,b,c,d`` -- basis elements of the associated :class:`FusionRing` + - ``a, b, c, d`` -- basis elements of the associated :class:`FusionRing` EXAMPLES:: @@ -677,7 +686,7 @@ def get_fvars(self): def get_poly_ring(self): r""" - Return the polynomial ring whose generators denote F-symbols we seek. + Return the polynomial ring whose generators denote the desired F-symbols. EXAMPLES:: @@ -1238,8 +1247,8 @@ def start_worker_pool(self, processes=None): INPUT: - ``processes`` -- an integer indicating the number of workers - in the pool. If left unspecified, the number of workers is - equals the number of processors available. + in the pool; if left unspecified, the number of workers is + equals the number of processors available OUTPUT: @@ -1319,7 +1328,7 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names, n_proc, pids_name return True def shutdown_worker_pool(self): - """ + r""" Shutdown the given worker pool and dispose of shared memory resources created when the pool was set up using :meth:`start_worker_pool`. @@ -1327,8 +1336,8 @@ def shutdown_worker_pool(self): Failure to call this method after using :meth:`start_worker_pool` to create a process pool may result in a memory - leak, since shared memory resources outlive the process that created - them. + leak, since shared memory resources outlive the process that + created them. EXAMPLES:: @@ -1354,7 +1363,7 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, INPUT: - -``mapper`` -- string specifying the name of a function defined in + - ``mapper`` -- string specifying the name of a function defined in the ``fast_parallel_fmats_methods`` module .. NOTE:: @@ -1839,7 +1848,9 @@ def _get_component_variety(self,var,eqns): ### Solution method ### ####################### - #TODO: this can probably be improved by constructing a set of defining polynomials and checking, one by one, if it's irreducible over the current field. If it is, we construct an extension. Perhaps it's best to go one by one here... + # TODO: this can probably be improved by constructing a set of defining polynomials + # and checking, one by one, if it's irreducible over the current field. + # If it is, we construct an extension. Perhaps it's best to go one by one here... def attempt_number_field_computation(self): r""" Based on the ``CartanType`` of ``self`` and data @@ -1895,6 +1906,8 @@ def attempt_number_field_computation(self): def _get_explicit_solution(self,eqns=None,verbose=True): r""" + Construct an explicit solution of ``self``. + When this method is called, the solution is already found in terms of Groeber basis. A few degrees of freedom remain. By specializing the free variables and back substituting, a @@ -2210,7 +2223,7 @@ def _fix_gauge(self, algorithm=""): adding equation... fx18 - 1 adding equation... fx21 - 1 """ - while sum(1 for v in self._solved if not v) > 0: + while not all(v for v in self._solved): #Get a variable that has not been fixed #In ascending index order, for consistent results for i, var in enumerate(self._poly_ring.gens()): @@ -2283,7 +2296,7 @@ def _update_equations(self): sage: f.ideal_basis {fx3} """ - special_values = {known : self._fvars[self._var_to_sextuple[known]] for known in self._solved if known} + special_values = {known: self._fvars[self._var_to_sextuple[known]] for known in self._solved if known} self.ideal_basis = set(eq.subs(special_values) for eq in self.ideal_basis) self.ideal_basis.discard(0) @@ -2369,45 +2382,45 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o ##################### def fmats_are_orthogonal(self): - r""" - Verify that all F-matrices are orthogonal. + r""" + Verify that all F-matrices are orthogonal. - This method should always return ``True`` when called after running - :meth:`find_orthogonal_solution`. + This method should always return ``True`` when called after running + :meth:`find_orthogonal_solution`. - EXAMPLES:: + EXAMPLES:: - sage: f = FMatrix(FusionRing("D4", 1)) - sage: f.find_orthogonal_solution(verbose=False) - sage: f.fmats_are_orthogonal() - True - """ - is_orthog = [] - for a,b,c,d in product(self._FR.basis(),repeat=4): - mat = self.fmatrix(a,b,c,d) - is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) - return all(is_orthog) + sage: f = FMatrix(FusionRing("D4", 1)) + sage: f.find_orthogonal_solution(verbose=False) + sage: f.fmats_are_orthogonal() + True + """ + is_orthog = [] + for a,b,c,d in product(self._FR.basis(),repeat=4): + mat = self.fmatrix(a,b,c,d) + is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) + return all(is_orthog) def fvars_are_real(self): - r""" - Test whether all F-symbols are real. - - EXAMPLES:: - - sage: f = FMatrix(FusionRing("A1", 3)) - sage: f.find_orthogonal_solution(verbose=False) # long time - sage: f.fvars_are_real() # not tested (cypari issue in doctesting framework) - True - """ - try: - for k, v in self._fvars.items(): - AA(self._qqbar_embedding(v)) - except ValueError: - print("the F-symbol {} (key {}) has a nonzero imaginary part!".format(v,k)) - return False - return True - - def certify_pentagons(self,use_mp=True,verbose=False): + r""" + Test whether all F-symbols are real. + + EXAMPLES:: + + sage: f = FMatrix(FusionRing("A1", 3)) + sage: f.find_orthogonal_solution(verbose=False) # long time + sage: f.fvars_are_real() # not tested (cypari issue in doctesting framework) + True + """ + try: + for k, v in self._fvars.items(): + AA(self._qqbar_embedding(v)) + except ValueError: + print("the F-symbol {} (key {}) has a nonzero imaginary part".format(v,k)) + return False + return True + + def certify_pentagons(self,use_mp=True, verbose=False): r""" Obtain a certificate of satisfaction for the pentagon equations, up to floating-point error. @@ -2442,8 +2455,8 @@ def certify_pentagons(self,use_mp=True,verbose=False): Partitioned 6 equations into 6 components of size: [1, 1, 1, 1, 1, 1] Computing appropriate NumberField... - sage: f.certify_pentagons() # not tested (long time ~1.5s, cypari issue in doctesting framework) - Success!!! Found valid F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients + sage: f.certify_pentagons() is None # not tested (long time ~1.5s, cypari issue in doctesting framework) + True """ fvars_copy = deepcopy(self._fvars) self._fvars = {sextuple: float(rhs) for sextuple, rhs in self.get_fvars_in_alg_field().items()} @@ -2452,13 +2465,15 @@ def certify_pentagons(self,use_mp=True,verbose=False): else: pool = None n_proc = pool._processes if pool is not None else 1 - params = [(child_id,n_proc,verbose) for child_id in range(n_proc)] - pe = self._map_triv_reduce('pent_verify',params,worker_pool=pool,chunksize=1,mp_thresh=0) + params = [(child_id, n_proc, verbose) for child_id in range(n_proc)] + pe = self._map_triv_reduce('pent_verify', params, worker_pool=pool, chunksize=1, mp_thresh=0) if np.all(np.isclose(np.array(pe),0,atol=1e-7)): - print("Success!!! Found valid F-symbols for {}".format(self._FR)) + if verbose: + print("Found valid F-symbols for {}".format(self._FR)) pe = None else: - print("Ooops... something went wrong... These pentagons remain:") - print(pe) + if verbose: + print("Something went wrong. Pentagons remain.") self._fvars = fvars_copy return pe + diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd index e0908ab5884..11dc0253b35 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd +++ b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd @@ -2,3 +2,4 @@ cdef _fmat(fvars, Nk_ij, one, a, b, c, d, x, y) cpdef _backward_subs(factory, bint flatten=*) cpdef executor(tuple params) cpdef _solve_for_linear_terms(factory, list eqns=*) + diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd index a992f0339a4..b3eec73b15b 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd @@ -1,2 +1,3 @@ cpdef _unflatten_entries(factory, list entries) cpdef executor(tuple params) + diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx index e82d36ce5d8..49af0008602 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx @@ -77,7 +77,7 @@ cdef odd_one_out_ij_cache = dict() cdef mid_sig_ij_cache = dict() cdef cached_mid_sig_ij(fusion_ring,row,col,a,b): - """ + r""" Cached version of :meth:`mid_sig_ij`. """ if (row,col,a,b) in mid_sig_ij_cache: @@ -87,7 +87,7 @@ cdef cached_mid_sig_ij(fusion_ring,row,col,a,b): return entry cdef cached_odd_one_out_ij(fusion_ring,xi,xj,a,b): - """ + r""" Cached version of :meth:`odd_one_out_ij`. """ if (xi,xj,a,b) in odd_one_out_ij_cache: @@ -178,7 +178,7 @@ cdef sig_2k(fusion_ring, tuple args): @cython.nonecheck(False) @cython.cdivision(True) cdef odd_one_out(fusion_ring, tuple args): - """ + r""" Compute entries of the rightmost braid generator, in case we have an odd number of strands. """ @@ -304,7 +304,7 @@ cpdef executor(tuple params): ###################################### cpdef _unflatten_entries(fusion_ring, list entries): - """ + r""" Restore cyclotomic coefficient object from its tuple of rational coefficients representation. diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx index be804b4da8b..a34fa303d15 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -150,7 +150,7 @@ cdef inline int has_appropriate_linear_term(tuple eq_tup): ###################### cpdef inline tup_to_univ_poly(tuple eq_tup, univ_poly_ring): - """ + r""" Given a tuple of pairs representing a univariate polynomial and a univariate polynomial ring, return a univariate polynomial object. @@ -178,7 +178,7 @@ cpdef inline tup_to_univ_poly(tuple eq_tup, univ_poly_ring): return univ_poly_ring({exp._data[1] if exp._nonzero else 0: c for exp, c in eq_tup}) cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): - """ + r""" Return a tuple representing a polynomial in a ring with ``len(sorted_vars)`` generators. diff --git a/src/sage/algebras/fusion_rings/shm_managers.pxd b/src/sage/algebras/fusion_rings/shm_managers.pxd index 32dd0073091..342b533acae 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pxd +++ b/src/sage/algebras/fusion_rings/shm_managers.pxd @@ -1,5 +1,4 @@ cimport numpy as np -import numpy as np from sage.rings.number_field.number_field_base cimport NumberField from sage.rings.number_field.number_field_element cimport NumberFieldElement_absolute @@ -22,3 +21,4 @@ cdef class FvarsHandler: cdef object fvars_t, pid_list cdef Py_ssize_t child_id cdef public object shm + diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 062a999fec4..21a9d0ea3eb 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -1,4 +1,4 @@ -""" +r""" Shared memory managers for F-symbol attributes. This module provides an implementation for shared dictionary like @@ -7,6 +7,7 @@ state attributes required by the orthogonal F-matrix solver. Currently, the attributes only work when the base field of the FMatrix factory is a cyclotomic field. """ + # **************************************************************************** # Copyright (C) 2021 Guillermo Aboumrad # @@ -15,8 +16,8 @@ factory is a cyclotomic field. # **************************************************************************** cimport cython -from cysignals.memory cimport sig_malloc cimport numpy as np +from cysignals.memory cimport sig_malloc from sage.algebras.fusion_rings.poly_tup_engine cimport poly_to_tup, tup_fixes_sq, _flatten_coeffs from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational @@ -27,51 +28,80 @@ import numpy as np from os import getpid cdef class KSHandler: + r""" + A shared memory backed dict-like structure to manage the + ``_ks`` attribute of an F-matrix. + + This structure implements a representation of the known squares dictionary + using a structured NumPy array backed by a contiguous shared memory + object. + + The structure mimics a dictionary of ``(idx, known_sq)`` pairs. Each + integer index corresponds to a variable and each ``known_sq`` is an + element of the F-matrix factory's base cyclotomic field. + + Each cyclotomic coefficient is stored as a list of numerators and a + list of denominators representing the rational coefficients. The + structured array also maintains ``known`` attribute that indicates + whether the structure contains an entry corresponding to the given index. + + The parent process should construct this object without a + ``name`` attribute. Children processes use the ``name`` attribute, + accessed via ``self.shm.name`` to attach to the shared memory block. + + INPUT: + + - ``n_slots`` -- the total number of F-symbols + - ``field`` -- F-matrix's base cyclotomic field + - ``use_mp`` -- a boolean indicating whether to construct a shared + memory block to back ``self``. Requires Python 3.8+, since we + must import the ``multiprocessing.shared_memory`` module. Attempting + to initialize when ``multiprocessing.shared_memory`` is not available + results in an ``ImportError``. + - ``name`` -- the name of a shared memory object + (used by child processes for attaching) + - ``init_data`` -- a dictionary or :class:`KSHandler` object containing + known squares for initialization, e.g., from a solver checkpoint + + .. NOTE:: + + To properly dispose of shared memory resources, + ``self.shm.unlink()`` must be called before exiting. + + .. WARNING:: + + This structure *cannot* modify an entry that + has already been set. + + EXAMPLES:: + + sage: from sage.algebras.fusion_rings.shm_managers import KSHandler + sage: #Create shared data structure + sage: f = FMatrix(FusionRing("A1",2), inject_variables=True) + creating variables fx1..fx14 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 + sage: n = f._poly_ring.ngens() + sage: is_shared_memory_available = f.start_worker_pool() + sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available) + sage: #In the same shell or in a different shell, attach to fvars + sage: name = ks.shm.name if is_shared_memory_available else None + sage: ks2 = KSHandler(n,f._field,name=name,use_mp=is_shared_memory_available) + sage: if not is_shared_memory_available: + ....: ks2 = ks + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup + sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] + sage: ks.update([poly_to_tup(p) for p in eqns]) + sage: for idx, sq in ks.items(): + ....: print("Index: {}, square: {}".format(idx, sq)) + ....: + Index: 1, square: 4 + Index: 3, square: -zeta32^4 + 1/19*zeta32^2 + sage: if is_shared_memory_available: ks.shm.unlink() + sage: f.shutdown_worker_pool() + """ def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): - """ - Return a shared memory backed dict-like structure to manage the - ``_ks`` attribute of an F-matrix factory object. - - This structure implements a representation of the known squares dictionary - using a structured NumPy array backed by a contiguous shared memory - object. - - The structure mimics a dictionary of ``(idx, known_sq)`` pairs. Each - integer index corresponds to a variable and each ``known_sq`` is an - element of the F-matrix factory's base cyclotomic field. - - Each cyclotomic coefficient is stored as a list of numerators and a - list of denominators representing the rational coefficients. The - structured array also maintains ``known`` attribute that indicates - whether the structure contains an entry corresponding to the given index. - - The parent process should construct this object without a - ``name`` attribute. Children processes use the ``name`` attribute, - accessed via ``self.shm.name`` to attach to the shared memory block. - - INPUT: - - - ``n_slots`` -- The total number of F-symbols. - - ``field`` -- F-matrix factory's base cyclotomic field. - - ``use_mp`` -- a boolean indicating whether to construct a shared - memory block to back ``self``. Requires Python 3.8+, since we - must import the ``multiprocessing.shared_memory`` module. Attempting - to initialize when ``multiprocessing.shared_memory`` is not available - results in an ``ImportError``. - - ``name`` -- the name of a shared memory object - (used by child processes for attaching). - - ``init_data`` -- a dictionary or :class:`KSHandler` object containing - known squares for initialization, e.g. from a solver checkpoint. - - .. NOTE:: - - To properly dispose of shared memory resources, - ``self.shm.unlink()`` must be called before exiting. - - .. WARNING:: - - This structure *cannot* modify an entry that - has already been set. + r""" + Initialize ``self``. EXAMPLES:: @@ -81,21 +111,9 @@ cdef class KSHandler: creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: is_shared_memory_available = f.start_worker_pool() sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available) - sage: #In the same shell or in a different shell, attach to fvars - sage: name = ks.shm.name if is_shared_memory_available else None - sage: ks2 = KSHandler(n,f._field,name=name,use_mp=is_shared_memory_available) - sage: if not is_shared_memory_available: - ....: ks2 = ks - sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] - sage: ks.update([poly_to_tup(p) for p in eqns]) - sage: for idx, sq in ks.items(): - ....: print("Index: {}, square: {}".format(idx, sq)) - ....: - Index: 1, square: 4 - Index: 3, square: -zeta32^4 + 1/19*zeta32^2 + sage: TestSuite(ks).run() sage: if is_shared_memory_available: ks.shm.unlink() sage: f.shutdown_worker_pool() """ @@ -134,7 +152,7 @@ cdef class KSHandler: @cython.wraparound(False) @cython.boundscheck(False) cdef NumberFieldElement_absolute get(self, int idx): - """ + r""" Retrieve the known square corresponding to the given index, if it exists. """ @@ -259,14 +277,14 @@ cdef class KSHandler: denoms[i] = denom cdef bint contains(self, int idx): - """ + r""" Determine whether ``self`` contains entry corresponding to given ``idx``. """ return self.ks_dat[idx]['known'] def __eq__(self, KSHandler other): - """ + r""" Test for equality. TESTS:: @@ -276,7 +294,7 @@ cdef class KSHandler: sage: f.get_orthogonality_constraints(output=False) sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: is_shared_memory_available = f.start_worker_pool() sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available,init_data=f._ks) sage: #In the same shell or in a different one, attach to shared memory handler sage: name = ks.shm.name if is_shared_memory_available else None @@ -288,13 +306,10 @@ cdef class KSHandler: sage: if is_shared_memory_available: ks.shm.unlink() sage: f.shutdown_worker_pool() """ - ret = True - for idx, sq in self.items(): - ret &= other.get(idx) == sq - return ret + return all(other.get(idx) == sq for idx, sq in self.items()) def __reduce__(self): - """ + r""" Provide pickling / unpickling support for ``self.`` TESTS:: @@ -308,10 +323,10 @@ cdef class KSHandler: True """ d = {i: sq for i, sq in self.items()} - return make_KSHandler, (self.ks_dat.size,self.field,d) + return make_KSHandler, (self.ks_dat.size, self.field, d) def items(self): - """ + r""" Iterate through existing entries using Python dict-style syntax. EXAMPLES:: @@ -338,91 +353,122 @@ cdef class KSHandler: yield i, self.get(i) def make_KSHandler(n_slots,field,init_data): - """ + r""" Provide pickling / unpickling support for :class:`KSHandler`. TESTS:: sage: f = FMatrix(FusionRing("B4",1)) sage: f._reset_solver_state() - sage: loads(dumps(f._ks)) == f._ks #indirect doctest + sage: loads(dumps(f._ks)) == f._ks # indirect doctest True - sage: f.find_orthogonal_solution(verbose=False) #long time - sage: loads(dumps(f._ks)) == f._ks #indirect doctest + sage: f.find_orthogonal_solution(verbose=False) # long time + sage: loads(dumps(f._ks)) == f._ks # indirect doctest True """ - return KSHandler(n_slots,field,init_data=init_data) + return KSHandler(n_slots, field, init_data=init_data) cdef class FvarsHandler: - def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, - pids_name=None, name=None, max_terms=20, n_bytes=32): - """ - Return a shared memory backed dict-like structure to manage the - ``_fvars`` attribute of an F-matrix factory object. - - This structure implements a representation of the F-symbols dictionary - using a structured NumPy array backed by a contiguous shared memory - object. - - The monomial data is stored in the ``exp_data`` structure. Monomial - exponent data is stored contiguously and ``ticks`` are used to - indicate different monomials. - - Coefficient data is stored in the ``coeff_nums`` and ``coeff_denom`` - arrays. The ``coeff_denom`` array stores the value - ``d = coeff.denominator()`` for each cyclotomic coefficient. The - ``coeff_nums`` array stores the values - ``c.numerator() * d for c in coeff._coefficients()``, the abridged - list representation of the cyclotomic coefficient ``coeff``. - - Each entry also has a boolean ``modified`` attribute, indicating - whether it has been modified by the parent process. Entry retrieval - is cached in each process, so each process must check whether - entries have been modified before attempting retrieval. - - The parent process should construct this object without a - ``name`` attribute. Children processes use the ``name`` attribute, - accessed via ``self.shm.name`` to attach to the shared memory block. - - INPUT: - - - ``factory`` -- an F-matrix factory object - - ``name`` -- the name of a shared memory object - (used by child processes for attaching) - - ``max_terms`` -- maximum number of terms in each entry; since - we use contiguous C-style memory blocks, the size of the block - must be known in advance - - ``use_mp`` -- an integer indicating the number of child processes - used for multiprocessing; if running serially, use 0. - - Multiprocessing requires Python 3.8+, since we must import the - ``multiprocessing.shared_memory`` module. Attempting to initialize - when ``multiprocessing.shared_memory`` is not available results in - an ``ImportError``. - - - ``pids_name`` -- the name of a ``ShareableList`` contaning the - process ``pid``'s for every process in the pool (including the - parent process) - - ``n_bytes`` -- the number of bytes that should be allocated for - each numerator and each denominator stored by the structure + r""" + A shared memory backed dict-like structure to manage the + ``_fvars`` attribute of an F-matrix. - .. NOTE:: + This structure implements a representation of the F-symbols dictionary + using a structured NumPy array backed by a contiguous shared memory + object. - To properly dispose of shared memory resources, - ``self.shm.unlink()`` must be called before exiting. + The monomial data is stored in the ``exp_data`` structure. Monomial + exponent data is stored contiguously and ``ticks`` are used to + indicate different monomials. - .. NOTE:: + Coefficient data is stored in the ``coeff_nums`` and ``coeff_denom`` + arrays. The ``coeff_denom`` array stores the value + ``d = coeff.denominator()`` for each cyclotomic coefficient. The + ``coeff_nums`` array stores the values + ``c.numerator() * d for c in coeff._coefficients()``, the abridged + list representation of the cyclotomic coefficient ``coeff``. - If you ever encounter an ``OverflowError`` when running the - :meth:`FMatrix.find_orthogonal_solution`` solver, consider - increasing the parameter ``n_bytes``. + Each entry also has a boolean ``modified`` attribute, indicating + whether it has been modified by the parent process. Entry retrieval + is cached in each process, so each process must check whether + entries have been modified before attempting retrieval. - .. WARNING:: + The parent process should construct this object without a + ``name`` attribute. Children processes use the ``name`` attribute, + accessed via ``self.shm.name`` to attach to the shared memory block. + + INPUT: + + - ``factory`` -- an F-matrix + - ``name`` -- the name of a shared memory object + (used by child processes for attaching) + - ``max_terms`` -- maximum number of terms in each entry; since + we use contiguous C-style memory blocks, the size of the block + must be known in advance + - ``use_mp`` -- an integer indicating the number of child processes + used for multiprocessing; if running serially, use 0. + + Multiprocessing requires Python 3.8+, since we must import the + ``multiprocessing.shared_memory`` module. Attempting to initialize + when ``multiprocessing.shared_memory`` is not available results in + an ``ImportError``. + + - ``pids_name`` -- the name of a ``ShareableList`` contaning the + process ``pid``'s for every process in the pool (including the + parent process) + - ``n_bytes`` -- the number of bytes that should be allocated for + each numerator and each denominator stored by the structure + + .. NOTE:: + + To properly dispose of shared memory resources, + ``self.shm.unlink()`` must be called before exiting. + + .. NOTE:: + + If you ever encounter an ``OverflowError`` when running the + :meth:`FMatrix.find_orthogonal_solution` solver, consider + increasing the parameter ``n_bytes``. + + .. WARNING:: - The current data structure supports up to `2^16` entries, - with each monomial in each entry having at most 254 - nonzero terms. On average, each of the ``max_terms`` monomials - can have at most 30 terms. + The current data structure supports up to `2^16` entries, + with each monomial in each entry having at most 254 + nonzero terms. On average, each of the ``max_terms`` monomials + can have at most 30 terms. + + EXAMPLES:: + + sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler + sage: #Create shared data structure + sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) + creating variables fx1..fx8 + Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 + sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: if is_shared_memory_available: + ....: n_proc = f.pool._processes + ....: pids_name = f._pid_list.shm.name + ....: else: + ....: n_proc = 0 + ....: pids_name = None + sage: fvars = FvarsHandler(8, f._field, f._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name) + sage: #In the same shell or in a different shell, attach to fvars + sage: name = fvars.shm.name if is_shared_memory_available else None + sage: fvars2 = FvarsHandler(8, f._field, f._idx_to_sextuple, name=name ,use_mp=n_proc, pids_name=pids_name) + sage: if not is_shared_memory_available: + ....: fvars2 = fvars + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup + sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) + sage: fvars[f2, f1, f2, f2, f0, f0] = rhs + sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) + fx5^5 + sage: if is_shared_memory_available: fvars.shm.unlink() + sage: f.shutdown_worker_pool() + """ + def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, + pids_name=None, name=None, max_terms=20, n_bytes=32): + r""" + Initialize ``self``. EXAMPLES:: @@ -439,16 +485,7 @@ cdef class FvarsHandler: ....: n_proc = 0 ....: pids_name = None sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) - sage: #In the same shell or in a different shell, attach to fvars - sage: name = fvars.shm.name if is_shared_memory_available else None - sage: fvars2 = FvarsHandler(8,f._field,f._idx_to_sextuple,name=name,use_mp=n_proc,pids_name=pids_name) - sage: if not is_shared_memory_available: - ....: fvars2 = fvars - sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) - sage: fvars[f2, f1, f2, f2, f0, f0] = rhs - sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) - fx5^5 + sage: TestSuite(fvars).run() sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ @@ -611,7 +648,7 @@ cdef class FvarsHandler: @cython.nonecheck(False) @cython.wraparound(False) def __setitem__(self, sextuple, fvar): - """ + r""" Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, create or overwrite an entry in the shared data structure corresponding to the given sextuple. @@ -623,7 +660,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: is_shared_memory_available = f.start_worker_pool() sage: if is_shared_memory_available: ....: n_proc = f.pool._processes ....: pids_name = f._pid_list.shm.name @@ -700,7 +737,7 @@ cdef class FvarsHandler: modified[:] = 1 def __reduce__(self): - """ + r""" Provide pickling / unpickling support for ``self.`` TESTS:: @@ -708,7 +745,7 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("F4",1)) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: is_shared_memory_available = f.start_worker_pool() sage: if is_shared_memory_available: ....: n_proc = f.pool._processes ....: pids_name = f._pid_list.shm.name @@ -728,7 +765,7 @@ cdef class FvarsHandler: return make_FvarsHandler, (n,self.field,idx_map,d) def items(self): - """ + r""" Iterates through key-value pairs in the data structure as if it were a Python dict. @@ -752,7 +789,7 @@ cdef class FvarsHandler: yield sextuple, self[sextuple] def make_FvarsHandler(n,field,idx_map,init_data): - """ + r""" Provide pickling / unpickling support for :class:`FvarsHandler`. TESTS:: @@ -760,7 +797,7 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: f = FMatrix(FusionRing("G2",1)) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: is_shared_memory_available = f.start_worker_pool() sage: if is_shared_memory_available: ....: n_proc = f.pool._processes ....: pids_name = f._pid_list.shm.name diff --git a/src/sage/combinat/root_system/all.py b/src/sage/combinat/root_system/all.py index ebcf08bf0c6..263a4d916d2 100644 --- a/src/sage/combinat/root_system/all.py +++ b/src/sage/combinat/root_system/all.py @@ -76,7 +76,7 @@ --------------------- - :ref:`sage.combinat.root_system.weyl_characters` -- :ref:`sage.combinat.root_system.fusion_ring` +- :ref:`sage.algebras.fusion_rings.fusion_ring` - :ref:`sage.combinat.root_system.integrable_representations` - :ref:`sage.combinat.root_system.branching_rules` - :ref:`sage.combinat.root_system.hecke_algebra_representation` From 3f9045d6e677267ab3c49bb91ddf9f1887f872bb Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 17 Aug 2022 11:31:02 +0900 Subject: [PATCH 138/414] Implementing infinite q-Pochhammer symbol as a lazy Laurent series. --- src/sage/rings/lazy_series.py | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index c5034928ab6..5be58d12b32 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1911,6 +1911,58 @@ def coeff(n, c): valuation=0) return f(self) + def q_pochhammer(self, q=None): + r""" + Return the infinite ``q``-Pochhammer symbol `(a; q)_{\infty}`, + where `a` is ``self``. + + This is also one version of the quantum dilogarithm (see + :wikipedia:`Quantum_dilogarithm`) or + the `q`-Exponential function (see :wikipedia:`Q-exponential`). + + INPUT: + + - ``q`` -- (default: `q \in \QQ(q)`) the parameter `q` + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: L. = LazyLaurentSeriesRing(q.parent()) + sage: qpoch = z.q_pochhammer(q) + sage: qpoch + 1 + + (-1/(-q + 1))*z + + (q/(q^3 - q^2 - q + 1))*z^2 + + (-q^3/(-q^6 + q^5 + q^4 - q^2 - q + 1))*z^3 + + (q^6/(q^10 - q^9 - q^8 + 2*q^5 - q^2 - q + 1))*z^4 + + (-q^10/(-q^15 + q^14 + q^13 - q^10 - q^9 - q^8 + q^7 + q^6 + q^5 - q^2 - q + 1))*z^5 + + (q^15/(q^21 - q^20 - q^19 + q^16 + 2*q^14 - q^12 - q^11 - q^10 - q^9 + 2*q^7 + q^5 - q^2 - q + 1))*z^6 + + O(z^7) + + We show that `(z; q)_n = \frac{(z; q)_{\infty}}{(q^n z; q)_{\infty}}`:: + + sage: qpoch / qpoch(q*z) + 1 - z + O(z^7) + sage: qpoch / qpoch(q^2*z) + 1 + (-q - 1)*z + q*z^2 + O(z^7) + sage: qpoch / qpoch(q^3*z) + 1 + (-q^2 - q - 1)*z + (q^3 + q^2 + q)*z^2 - q^3*z^3 + O(z^7) + sage: qpoch / qpoch(q^4*z) + 1 + (-q^3 - q^2 - q - 1)*z + (q^5 + q^4 + 2*q^3 + q^2 + q)*z^2 + + (-q^6 - q^5 - q^4 - q^3)*z^3 + q^6*z^4 + O(z^7) + """ + if q is None: + q = ZZ['q'].fraction_field().gen() + from .lazy_series_ring import LazyLaurentSeriesRing + from sage.arith.misc import binomial + qP = q.parent() + L = LazyLaurentSeriesRing(qP, "z", sparse=self.parent()._sparse) + one = qP.one() + def coeff(n): + return (-1)**n * q**binomial(n,2) / qP.prod(one - q**i for i in range(1, n+1)) + f = L(coeff, valuation=0) + return f(self) + # === powers === def __pow__(self, n): From 80bc4e5886e18fab4d5de332b8cc41bbb8eebe08 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 17 Aug 2022 12:07:46 +0900 Subject: [PATCH 139/414] Adding more documetnation and Euler's function. --- src/sage/rings/lazy_series.py | 67 ++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 5be58d12b32..f105380f2ed 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1911,14 +1911,15 @@ def coeff(n, c): valuation=0) return f(self) + # === named special functions === + def q_pochhammer(self, q=None): r""" Return the infinite ``q``-Pochhammer symbol `(a; q)_{\infty}`, where `a` is ``self``. - This is also one version of the quantum dilogarithm (see - :wikipedia:`Quantum_dilogarithm`) or - the `q`-Exponential function (see :wikipedia:`Q-exponential`). + This is also one version of the quantum dilogarithm or + the `q`-Exponential function. INPUT: @@ -1950,6 +1951,19 @@ def q_pochhammer(self, q=None): sage: qpoch / qpoch(q^4*z) 1 + (-q^3 - q^2 - q - 1)*z + (q^5 + q^4 + 2*q^3 + q^2 + q)*z^2 + (-q^6 - q^5 - q^4 - q^3)*z^3 + q^6*z^4 + O(z^7) + + We can also construct part of Euler's function:: + + sage: M. = LazyLaurentSeriesRing(QQ) + sage: phi = sum(qpoch[i](q=a)*a^i for i in range(10)) + sage: phi[:20] == a.euler()[:20] + True + + REFERENCES: + + - :wikipedia:`Q-Pochhammer_symbol` + - :wikipedia:`Quantum_dilogarithm` + - :wikipedia:`Q-exponential` """ if q is None: q = ZZ['q'].fraction_field().gen() @@ -1959,10 +1973,55 @@ def q_pochhammer(self, q=None): L = LazyLaurentSeriesRing(qP, "z", sparse=self.parent()._sparse) one = qP.one() def coeff(n): - return (-1)**n * q**binomial(n,2) / qP.prod(one - q**i for i in range(1, n+1)) + return (-1)**n * q**binomial(n, 2) / qP.prod(one - q**i for i in range(1, n+1)) f = L(coeff, valuation=0) return f(self) + def euler(self, q=None): + r""" + Return the Euler function evaluated at ``self``. + + The *Euler function* is defined as + + .. MATH:: + + \phi(z) = (z; z)_{\infty} + = \sum_{n=0}^{\infty} (-1)^n q^{(3n^2-n)/2}. + + INPUT: + + - ``q`` -- (default: `q \in \QQ(q)`) the parameter `q` + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: phi = q.euler() + sage: phi + 1 - q - q^2 + q^5 + O(q^7) + + We verify that `1 / phi` gives the generating function + for all partitions:: + + sage: P = 1 / phi; P + 1 + q + 2*q^2 + 3*q^3 + 5*q^4 + 7*q^5 + 11*q^6 + O(q^7) + sage: P[:20] == [Partitions(n).cardinality() for n in range(20)] + True + + REFERENCES: + + - :wikipedia:`Euler_function` + """ + from .lazy_series_ring import LazyLaurentSeriesRing + L = LazyLaurentSeriesRing(ZZ, "q", sparse=self.parent()._sparse) + def coeff(n): + k = ZZ(24 * n + 1) + m, rem = k.sqrtrem() + if rem: + return ZZ.zero() + return (-1) ** ((m + 1) // 6) + phi = L(coeff, valuation=0) + return phi(self) + # === powers === def __pow__(self, n): From 37a0c7ff2789bfacf767568e619b1de3c1109503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 20 Jun 2022 15:07:55 +0200 Subject: [PATCH 140/414] first sketch for FHM triangles --- src/doc/en/reference/combinat/module_list.rst | 1 + src/sage/combinat/posets/posets.py | 51 +- src/sage/combinat/triangles_FHM.py | 678 ++++++++++++++++++ src/sage/topology/simplicial_complex.py | 39 + 4 files changed, 767 insertions(+), 2 deletions(-) create mode 100644 src/sage/combinat/triangles_FHM.py diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 7a97a40e30d..8264d7c4521 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -362,6 +362,7 @@ Comprehensive Module List sage/combinat/tamari_lattices sage/combinat/tiling sage/combinat/tools + sage/combinat/triangles_FHM sage/combinat/tuple sage/combinat/tutorial sage/combinat/vector_partition diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 2c236e4a4ae..25b8e110181 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -184,6 +184,7 @@ :meth:`~FinitePoset.flag_h_polynomial` | Return the flag h-polynomial of the poset. :meth:`~FinitePoset.order_polynomial` | Return the order polynomial of the poset. :meth:`~FinitePoset.zeta_polynomial` | Return the zeta polynomial of the poset. + :meth:`~FinitePoset.M_triangle` | Return the M-triangle of the poset. :meth:`~FinitePoset.kazhdan_lusztig_polynomial` | Return the Kazhdan-Lusztig polynomial of the poset. :meth:`~FinitePoset.coxeter_polynomial` | Return the characteristic polynomial of the Coxeter transformation. :meth:`~FinitePoset.degree_polynomial` | Return the generating polynomial of degrees of vertices in the Hasse diagram. @@ -7257,6 +7258,47 @@ def zeta_polynomial(self): f = g[n] + f / n return f + def M_triangle(self): + r""" + Return the M-triangle of the poset. + + The poset is expected to be graded. + + OUTPUT: + + an :class:`~sage.combinat.triangles_FHM.M_triangle` + + Thie M-triangle is the generating polynomial of the Möbius numbers + + .. MATH:: + + M(x, y)=\sum_{a \leq b} \mu(a,b) x^{|a|}y^{|b|} . + + EXAMPLES:: + + sage: P = posets.DiamondPoset(5) + sage: P.M_triangle() + x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 + + TESTS:: + + sage: P = posets.PentagonPoset() + sage: P.M_triangle() + Traceback (most recent call last): + ... + ValueError: the poset is not graded + """ + from sage.combinat.triangles_FHM import M_triangle + hasse = self._hasse_diagram + rk = hasse.rank_function() + if rk is None: + raise ValueError('the poset is not graded') + x, y = polygen(ZZ, 'x,y') + p = sum(hasse.moebius_function(a, b) * x**rk(a) * y**rk(b) + for a in hasse + for b in hasse.principal_order_filter(a)) + return M_triangle(p) + def f_polynomial(self): r""" Return the `f`-polynomial of the poset. @@ -7294,12 +7336,17 @@ def f_polynomial(self): sage: P = Poset({2: []}) sage: P.f_polynomial() 1 + + sage: P = Poset({2:[1,3]}) + sage: P.f_polynomial() + Traceback (most recent call last): + ... + ValueError: the poset is not bounded """ q = polygen(ZZ, 'q') - one = q.parent().one() hasse = self._hasse_diagram if len(hasse) == 1: - return one + return q.parent().one() maxi = hasse.top() mini = hasse.bottom() if mini is None or maxi is None: diff --git a/src/sage/combinat/triangles_FHM.py b/src/sage/combinat/triangles_FHM.py new file mode 100644 index 00000000000..1f9f830ef9b --- /dev/null +++ b/src/sage/combinat/triangles_FHM.py @@ -0,0 +1,678 @@ +""" +Combinatorial triangles for posets and fans + +This provides several classes and methods to convert between them. +Elements of the classes are polynomials in two variables `x` and `y`, +possibly with other parameters. The conversion methods amount to specific +invertible rational change-of-variables involving `x` and `y`. + +The M-triangle class is motivated by the generating series of Möbius numbers +for graded posets. A typical example is:: + + sage: W = SymmetricGroup(4) + sage: posets.NoncrossingPartitions(W).M_triangle() + x^3*y^3 - 6*x^2*y^3 + 6*x^2*y^2 + 10*x*y^3 - 16*x*y^2 - 5*y^3 + + 6*x*y + 10*y^2 - 6*y + 1 + +The F-triangle class is motivated by the generating series of pure +simplicial complexes endowed with a distinguished facet. One can also +think about complete fans endowed with a distinguished maximal +cone. A typical example is:: + + sage: C = ClusterComplex(['A',3]) + sage: f = C.greedy_facet() + sage: C.F_triangle(f) + 5*x^3 + 5*x^2*y + 3*x*y^2 + y^3 + 10*x^2 + 8*x*y + 3*y^2 + 6*x + 3*y + 1 + +The H-triangles are related to the F-triangles by a relationship +similar to the classical link between the f-vector and the h-vector of a +simplicial complex. + +The Gamma-triangles are related to the H-triangles by an +analog of the relationship between gamma-vectors and h-vectors of flag +simplicial complexes. +""" +from sage.matrix.constructor import matrix +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring import polygen +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.structure.sage_object import SageObject + + +def _matrix_display(self, variables=None): + """ + Return the 2-variables polynomial ``self`` as a matrix for display. + + INPUT: + + - ``variables`` -- optional choice of 2 variables + + OUPUT: + + matrix + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import _matrix_display + sage: x, y = PolynomialRing(QQ,['x', 'y']).gens() + sage: _matrix_display(x**2+x*y+y**3) + [1 0 0] + [0 0 0] + [0 1 0] + [0 0 1] + + With a specific choice of variables:: + + sage: x, y, z = PolynomialRing(QQ,['x','y','z']).gens() + sage: _matrix_display(x**2+z*x*y+z*y**3+z*x,[y,z]) + [ x x 0 1] + [x^2 0 0 0] + sage: _matrix_display(x**2+z*x*y+z*y**3+z*x,[x,z]) + [ y^3 y + 1 0] + [ 0 0 1] + """ + support = self.exponents() + if variables is None: + ring = self.parent().base_ring() + x, y = self.parent().gens() + ix = 0 + iy = 1 + else: + x, y = variables + ring = self.parent() + toutes_vars = x.parent().gens() + ix = toutes_vars.index(x) + iy = toutes_vars.index(y) + minx = min(u[ix] for u in support) + maxy = max(u[iy] for u in support) + deltax = max(u[ix] for u in support) - minx + 1 + deltay = maxy - min(u[iy] for u in support) + 1 + mat = matrix(ring, deltay, deltax) + for u in support: + ex = u[ix] + ey = u[iy] + mat[maxy - ey, ex - minx] = self.coefficient({x: ex, y: ey}) + return mat + + +class Triangle(SageObject): + """ + Common class for different kinds of triangles. + + This serves as a base class for F-triangles, H-triangles, M-triangles + and Gamma-triangles. + + The user should use these subclasses directly. + + The input is a polynomial in two variables. One can also give a + polynomial with more variables and specify two chosen variables. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import Triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: ht = Triangle(1+4*x+2*x*y) + sage: unicode_art(ht) + ⎛0 2⎞ + ⎝1 4⎠ + """ + def __init__(self, poly, variables=None): + """ + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import Triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: ht = Triangle(1+2*x*y) + sage: unicode_art(ht) + ⎛0 2⎞ + ⎝1 0⎠ + """ + if variables is None: + self._vars = poly.parent().gens() + else: + self._vars = variables + self._poly = poly + self._n = max(self._poly.degree(v) for v in self._vars) + + def _unicode_art_(self): + """ + Return the unicode representation (as a matrix). + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: ht = H_triangle(1+2*x*y) + sage: unicode_art(ht) + ⎛0 2⎞ + ⎝1 0⎠ + """ + return self.matrix()._unicode_art_() + + def _repr_(self) -> str: + """ + Return the string representation (as a polynomial). + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: ht = H_triangle(1+2*x*y) + sage: ht + 2*x*y + 1 + """ + return repr(self._poly) + + def __eq__(self, other) -> bool: + """ + Test for equality. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h1 = H_triangle(1+2*x*y) + sage: h2 = H_triangle(1+3*x*y) + sage: h1 == h1 + True + sage: h1 == h2 + False + """ + if isinstance(other, Triangle): + return self._poly == other._poly + return self._poly == other + + def __ne__(self, other) -> bool: + """ + Test for unequality. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h1 = H_triangle(1+2*x*y) + sage: h2 = H_triangle(1+3*x*y) + sage: h1 != h1 + False + sage: h1 != h2 + True + """ + return not self == other + + def __call__(self, *args): + """ + Return the evaluation (as a polynomial). + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h = H_triangle(1+3*x*y) + sage: h(4,5) + 61 + """ + return self._poly(*args) + + def __getitem__(self, *args): + """ + Return some coefficient. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h = H_triangle(1+2*x+3*x*y) + sage: h[1,1] + 3 + """ + return self._poly.__getitem__(*args) + + def __hash__(self): + """ + Return the hash value. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h = H_triangle(1+2*x*y) + sage: g = H_triangle(1+2*x*y) + sage: hash(h) == hash(g) + True + """ + return hash(self._poly) + + def matrix(self): + """ + Return the associated matrix for display. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h = H_triangle(1+2*x*y) + sage: h.matrix() + [0 2] + [1 0] + """ + return _matrix_display(self._poly, variables=self._vars) + + def truncate(self, d): + """ + Return the truncated triangle. + + INPUT: + + - ``d`` -- integer + + As a polynomial, this means that all monomials with a power + of either `x` or `y` greater than or equal to ``d`` are dismissed. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h = H_triangle(1+2*x*y) + sage: h.truncate(2) + 2*x*y + 1 + """ + p = self._poly + for v in self._vars: + p = p.truncate(v, d) + return self.__class__(p, self._vars) + + +class M_triangle(Triangle): + """ + Class for the M-triangles. + + This is motivated by generating series of Möbius numbers of graded posets. + + EXAMPLES:: + + sage: x, y = polygens(ZZ, 'x,y') + sage: P = Poset({2:[1]}) + sage: P.M_triangle() + x*y - y + 1 + """ + def dual(self): + """ + Return the dual M-triangle. + + This is the M-triangle of the dual poset, hence an involution. + + When seen as a matrix, this performs a symmetry with respect to the + northwest-southeast diagonal. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import M_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: mt = M_triangle(x*y - y + 1) + sage: mt.dual() == mt + True + """ + x, y = self._vars + n = self._n + A = self._poly.parent() + + dict_dual = {(n - dy, n - dx): coeff + for (dx, dy), coeff in self._poly.dict().items()} + return M_triangle(A(dict_dual), variables=(x, y)) + + def transpose(self): + """ + Return the image of ``self`` by an involution. + + OUTPUT: + + another M-triangle + + The involution is defined by converting to an H-triangle, + transposing the matrix, and then converting back to an M-triangle. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import M_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: nc3 = x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 + sage: m = M_triangle(nc3) + sage: m2 = m.transpose(); m2 + 2*x^2*y^2 - 3*x*y^2 + 2*x*y + y^2 - 2*y + 1 + sage: m2.transpose() == m + True + """ + return self.h().transpose().m() + + def h(self): + """ + Return the associated H-triangle. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import M_triangle + sage: x, y = polygens(ZZ,'x,y') + sage: M_triangle(1-y+x*y).h() + x*y + 1 + + TESTS:: + + sage: h = polygen(ZZ, 'h') + sage: x, y = polygens(h.parent(),'x,y') + sage: mt = x**2*y**2+(-2*h+2)*x*y**2+(2*h-2)*x*y+(2*h-3)*y**2+(-2*h+2)*y+1 + sage: M_triangle(mt, [x,y]).h() + x^2*y^2 + 2*x*y + (2*h - 4)*x + 1 + """ + x, y = self._vars + n = self._n + step = self._poly(x=y / (y - 1), y=(y - 1) * x / (1 + (y - 1) * x)) + step *= (1 + (y - 1) * x)**n + polyh = step.numerator() + return H_triangle(polyh, variables=(x, y)) + + def f(self): + """ + Return the associated F-triangle. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import M_triangle + sage: x, y = polygens(ZZ,'x,y') + sage: M_triangle(1-y+x*y).f() + x + y + 1 + + TESTS:: + + sage: h = polygen(ZZ, 'h') + sage: x, y = polygens(h.parent(),'x,y') + sage: mt = x**2*y**2+(-2*h+2)*x*y**2+(2*h-2)*x*y+(2*h-3)*y**2+(-2*h+2)*y+1 + sage: M_triangle(mt, [x,y]).f() + (2*h - 3)*x^2 + 2*x*y + y^2 + (2*h - 2)*x + 2*y + 1 + """ + return self.h().f() + + +class H_triangle(Triangle): + """ + Class for the H-triangles. + """ + def transpose(self): + """ + Return the transposed H-triangle. + + OUTPUT: + + another H-triangle + + This operation is an involution. When seen as a matrix, it + performs a symmetry with respect to the northwest-southeast + diagonal. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ,'x,y') + sage: H_triangle(1+x*y).transpose() + x*y + 1 + sage: H_triangle(x^2*y^2 + 2*x*y + x + 1).transpose() + x^2*y^2 + x^2*y + 2*x*y + 1 + """ + x, y = self._vars + n = self._n + A = self._poly.parent() + + dict_dual = {(n - dy, n - dx): coeff + for (dx, dy), coeff in self._poly.dict().items()} + return H_triangle(A(dict_dual), variables=(x, y)) + + def m(self): + """ + Return the associated M-triangle. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: h = polygen(ZZ, 'h') + sage: x, y = polygens(h.parent(),'x,y') + sage: ht = H_triangle(x^2*y^2 + 2*x*y + 2*x*h - 4*x + 1, variables=[x,y]) + sage: ht.m() + x^2*y^2 + (-2*h + 2)*x*y^2 + (2*h - 2)*x*y + (2*h - 3)*y^2 + + (-2*h + 2)*y + 1 + """ + x, y = self._vars + n = self._n + step = self._poly(x=(x - 1) * y / (1 - y), y=x / (x - 1)) * (1 - y)**n + polym = step.numerator() + return M_triangle(polym, variables=(x, y)) + + def f(self): + """ + Return the associated F-triangle. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ,'x,y') + sage: H_triangle(1+x*y).f() + x + y + 1 + sage: H_triangle(x^2*y^2 + 2*x*y + x + 1).f() + 2*x^2 + 2*x*y + y^2 + 3*x + 2*y + 1 + sage: flo = H_triangle(1+4*x+2*x**2+x*y*(4+8*x)+ + ....: x**2*y**2*(6+4*x)+4*(x*y)**3+(x*y)**4).f(); flo + 7*x^4 + 12*x^3*y + 10*x^2*y^2 + 4*x*y^3 + y^4 + 20*x^3 + 28*x^2*y + + 16*x*y^2 + 4*y^3 + 20*x^2 + 20*x*y + 6*y^2 + 8*x + 4*y + 1 + sage: flo(-1-x,-1-y) == flo + True + + TESTS:: + + sage: x,y,h = polygens(ZZ,'x,y,h') + sage: ht = x^2*y^2 + 2*x*y + 2*x*h - 4*x + 1 + sage: H_triangle(ht,[x,y]).f() + 2*x^2*h - 3*x^2 + 2*x*y + y^2 + 2*x*h - 2*x + 2*y + 1 + """ + x, y = self._vars + n = self._n + step1 = self._poly(x=x / (1 + x), y=y) * (x + 1)**n + step2 = step1(x=x, y=y / x) + polyf = step2.numerator() + return F_triangle(polyf, variables=(x, y)) + + def gamma(self): + """ + Return the associated Gamma-triangle. + + In some cases, this is a more condensed way to encode + the same amount of information. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygen(ZZ,'x,y') + sage: ht = x**2*y**2 + 2*x*y + x + 1 + sage: H_triangle(ht).gamma() + y^2 + x + + sage: W = SymmetricGroup(5) + sage: P = posets.NoncrossingPartitions(W) + sage: P.M_triangle().h().gamma() + y^4 + 3*x*y^2 + 2*x^2 + 2*x*y + x + """ + x, y = self._vars + n = self._n + remain = self._poly + gamma = x.parent().zero() + for k in range(n, -1, -1): + step = remain.coefficient({x: k}) + gamma += x**(n - k) * step + remain -= x**(n - k) * step.homogenize(x)(x=1 + x, y=1 + x * y) + return Gamma_triangle(gamma, variables=(x, y)) + + def vector(self): + """ + Return the h-vector as a polynomial in one variable. + + This is obtained by letting `y=1`. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygen(ZZ,'x,y') + sage: ht = x**2*y**2 + 2*x*y + x + 1 + sage: H_triangle(ht).vector() + x^2 + 3*x + 1 + """ + anneau = PolynomialRing(ZZ, 'x') + return anneau(self._poly(y=1)) + + +class F_triangle(Triangle): + """ + Class for the F-triangles. + """ + def h(self): + """ + Return the associated H-triangle. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import F_triangle + sage: x,y = polygens(ZZ,'x,y') + sage: ft = F_triangle(1+x+y) + sage: ft.h() + x*y + 1 + + TESTS:: + + sage: h = polygen(ZZ, 'h') + sage: x, y = polygens(h.parent(),'x,y') + sage: ft = 1+2*y+(2*h-2)*x+y**2+2*x*y+(2*h-3)*x**2 + sage: F_triangle(ft, [x,y]).h() + x^2*y^2 + 2*x*y + (2*h - 4)*x + 1 + """ + x, y = self._vars + n = self._n + step = (1 - x)**n * self._poly(x=x / (1 - x), y=x * y / (1 - x)) + polyh = step.numerator() + return H_triangle(polyh, variables=(x, y)) + + def m(self): + """ + Return the associated M-triangle. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ,'x,y') + sage: H_triangle(1+x*y).f() + x + y + 1 + sage: _.m() + x*y - y + 1 + + sage: H_triangle(x^2*y^2 + 2*x*y + x + 1).f() + 2*x^2 + 2*x*y + y^2 + 3*x + 2*y + 1 + sage: _.m() + x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 + + TESTS:: + + sage: p = 1+4*x+2*x**2+x*y*(4+8*x) + sage: p += x**2*y**2*(6+4*x)+4*(x*y)**3+(x*y)**4 + sage: flo = H_triangle(p).f(); flo + 7*x^4 + 12*x^3*y + 10*x^2*y^2 + 4*x*y^3 + y^4 + 20*x^3 + + 28*x^2*y + 16*x*y^2 + 4*y^3 + 20*x^2 + 20*x*y + 6*y^2 + + 8*x + 4*y + 1 + sage: flo.m() + x^4*y^4 - 8*x^3*y^4 + 8*x^3*y^3 + 20*x^2*y^4 + - 36*x^2*y^3 - 20*x*y^4 + + 16*x^2*y^2 + 48*x*y^3 + 7*y^4 - 36*x*y^2 - 20*y^3 + 8*x*y + + 20*y^2 - 8*y + 1 + + sage: from sage.combinat.triangles_FHM import F_triangle + sage: h = polygen(ZZ, 'h') + sage: x, y = polygens(h.parent(),'x,y') + sage: ft = F_triangle(1+2*y+(2*h-2)*x+y**2+2*x*y+(2*h-3)*x**2,(x,y)) + sage: ft.m() + x^2*y^2 + (-2*h + 2)*x*y^2 + (2*h - 2)*x*y + (2*h - 3)*y^2 + + (-2*h + 2)*y + 1 + """ + x, y = self._vars + n = self._n + step = self._poly(x=y * (x - 1) / (1 - x * y), y=x * y / (1 - x * y)) + step *= (1 - x * y)**n + polym = step.numerator() + return M_triangle(polym, variables=(x, y)) + + def vector(self): + """ + Return the f-vector as a polynomial in one variable. + + This is obtained by letting `y=x`. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import F_triangle + sage: x, y = polygen(ZZ,'x,y') + sage: ft = 2*x^2 + 2*x*y + y^2 + 3*x + 2*y + 1 + sage: F_triangle(ft).vector() + 5*x^2 + 5*x + 1 + """ + anneau = PolynomialRing(ZZ, 'x') + x = anneau.gen() + return anneau(self._poly(y=x)) + + + +class Gamma_triangle(Triangle): + """ + Class for the Gamma-triangles. + """ + def h(self): + r""" + Return the associated H-triangle. + + The transition between Gamma-triangles and H-triangles is defined by + + .. MATH:: + + H(x,y) = (1+x)^d \sum_{0\leq i; 0\leq j \leq d-2i} gamma_{i,j} + (\frac{x}{(1+x)^2})^i (\frac{1+xy}{1+x})^j + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import Gamma_triangle + sage: x, y = polygen(ZZ,'x,y') + sage: g = y**2 + x + sage: Gamma_triangle(g).h() + x^2*y^2 + 2*x*y + x + 1 + + sage: a, b = polygen(ZZ, 'a, b') + sage: x, y = polygens(a.parent(),'x,y') + sage: g = Gamma_triangle(y**3+a*x*y+b*x,(x,y)) + sage: hh = g.h() + sage: hh.gamma() == g + True + """ + x, y = self._vars + n = self._n + resu = (1 + x)**n * self._poly(x=x / (1 + x)**2, + y=(1 + x * y) / (1 + x)) + polyh = resu.numerator() + return H_triangle(polyh, variables=(x, y)) + + def vector(self): + """ + Return the gamma-vector as a polynomial in one variable. + + This is obtained by letting `y=1`. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import Gamma_triangle + sage: x, y = polygen(ZZ,'x,y') + sage: gt = y**2 + x + sage: Gamma_triangle(gt).vector() + x + 1 + """ + anneau = PolynomialRing(ZZ, 'x') + return anneau(self._poly(y=1)) diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index 6c000a8625b..4b07f5a79f2 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -165,6 +165,7 @@ from sage.structure.parent import Parent from sage.rings.integer import Integer from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.polynomial_ring import polygens from sage.sets.set import Set from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ @@ -1521,6 +1522,10 @@ def f_triangle(self): The `f`-triangle is given by `f_{i,j}` being the number of faces `F` of size `j` such that `i = \max_{G \subseteq F} |G|`. + .. SEEALSO:: + + Not to be confused with :meth:`F_triangle` . + EXAMPLES:: sage: X = SimplicialComplex([[1,2,3], [3,4,5], [1,4], [1,5], [2,4], [2,5]]) @@ -1577,6 +1582,40 @@ def h_triangle(self): for k in range(j+1)) return ret + def F_triangle(self, S): + """ + Return the F-triangle of ``self`` w.r.t one maximal simplex ``S``. + + This is the bivariate generating polynomial of all faces, + according to the number of elements in ``S`` and outside ``S``. + + OUTPUT: + + an :class:`~sage.combinat.triangles_FHM.F_triangle` + + .. SEEALSO:: + + Not to be confused with :meth:`f_triangle` . + + EXAMPLES:: + + sage: cs = simplicial_complexes.Torus() + sage: cs.F_triangle(cs.facets()[0]) + x^3 + 9*x^2*y + 3*x*y^2 + y^3 + 6*x^2 + 12*x*y + 3*y^2 + 4*x + 3*y + 1 + """ + x, y = polygens(ZZ, 'x, y') + from sage.combinat.triangles_FHM import F_triangle + + def nega(f): + return sum(1 for v in f if v in S) + + def posi(f): + return f.dimension() + 1 - nega(f) + + poly = sum(x**posi(fa) * y**nega(fa) + for fa in self.face_iterator()) + return F_triangle(poly) + def flip_graph(self): """ If ``self`` is pure, then it returns the flip graph of ``self``, From ab3df11b4c43a5e4855e13193b4eb803936a746d Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 20 Aug 2022 16:26:43 +0200 Subject: [PATCH 141/414] trac #34397: clean src/sage/graphs/generic_graph.py - part 9 --- src/sage/graphs/generic_graph.py | 122 ++++++++++++++++++------------- 1 file changed, 73 insertions(+), 49 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index f2c94041c51..fe877bdf359 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -460,7 +460,8 @@ class GenericGraph(GenericGraph_pyx): """ # Nice defaults for plotting arrays of graphs (see sage.misc.functional.show) - graphics_array_defaults = {'layout': 'circular', 'vertex_size':50, 'vertex_labels':False, 'graph_border':True} + graphics_array_defaults = {'layout': 'circular', 'vertex_size': 50, + 'vertex_labels': False, 'graph_border': True} def __init__(self): r""" @@ -601,16 +602,16 @@ def __eq__(self, other): if not isinstance(other, GenericGraph): return False from sage.graphs.graph import Graph - g1_is_graph = isinstance(self, Graph) # otherwise, DiGraph - g2_is_graph = isinstance(other, Graph) # otherwise, DiGraph + g1_is_graph = isinstance(self, Graph) # otherwise, DiGraph + g2_is_graph = isinstance(other, Graph) # otherwise, DiGraph # Fast checks if (g1_is_graph != g2_is_graph or - self.allows_multiple_edges() != other.allows_multiple_edges() or - self.allows_loops() != other.allows_loops() or - self.order() != other.order() or - self.size() != other.size() or - self.weighted() != other.weighted()): - return False + self.allows_multiple_edges() != other.allows_multiple_edges() or + self.allows_loops() != other.allows_loops() or + self.order() != other.order() or + self.size() != other.size() or + self.weighted() != other.weighted()): + return False return self._backend.is_subgraph(other._backend, self, ignore_labels=not self.weighted()) @@ -809,11 +810,18 @@ def _bit_vector(self): n = self.order() if self._directed: total_length = n * n - bit = lambda x, y: x * n + y + + def bit(x, y): + return x * n + y else: total_length = (n * (n - 1)) // 2 - n_ch_2 = lambda b: int(b * (b - 1)) // 2 - bit = lambda x, y: n_ch_2(max(x, y)) + min(x, y) + + def n_ch_2(b): + return int(b * (b - 1)) // 2 + + def bit(x, y): + return n_ch_2(max(x, y)) + min(x, y) + bit_vector = set() try: @@ -821,13 +829,13 @@ def _bit_vector(self): except TypeError: V = self v_to_int = {v: i for i, v in enumerate(V)} - for u,v,_ in self.edge_iterator(): + for u, v in self.edge_iterator(labels=False): bit_vector.add(bit(v_to_int[u], v_to_int[v])) bit_vector = sorted(bit_vector) s = [] j = 0 for i in bit_vector: - s.append( '0' * (i - j) + '1' ) + s.append('0' * (i - j) + '1') j = i + 1 s = "".join(s) s += '0' * (total_length - len(s)) @@ -921,7 +929,7 @@ def _repr_(self): name += "multi-" if self._directed: name += "di" - name += "graph on %d vert"%self.order() + name += "graph on %d vert" % self.order() if self.order() == 1: name += "ex" else: @@ -945,7 +953,7 @@ def is_immutable(self): """ return getattr(self, '_immutable', False) - ### Formats + # Formats def copy(self, weighted=None, data_structure=None, sparse=None, immutable=None): """ @@ -1164,7 +1172,7 @@ def copy(self, weighted=None, data_structure=None, sparse=None, immutable=None): (weighted is None or self._weighted == weighted)): from sage.graphs.base.static_sparse_backend import StaticSparseBackend if (isinstance(self._backend, StaticSparseBackend) and - (data_structure=='static_sparse' or data_structure is None)): + (data_structure == 'static_sparse' or data_structure is None)): return self if data_structure is None: @@ -1306,7 +1314,7 @@ def export_to_file(self, filename, format=None, **kwds): if format not in formats: raise ValueError("format '{}' unknown".format(format)) - formats[format](self.networkx_graph(),filename,**kwds) + formats[format](self.networkx_graph(), filename, **kwds) def _scream_if_not_simple(self, allow_loops=False, allow_multiple_edges=False): r""" @@ -1931,20 +1939,20 @@ def adjacency_matrix(self, sparse=None, vertices=None, *, base_ring=None, **kwds i = new_indices[u] j = new_indices[v] if multiple_edges and (i, j) in D: - D[i,j] += 1 + D[i, j] += 1 if not directed and i != j: - D[j,i] += 1 + D[j, i] += 1 else: - D[i,j] = 1 + D[i, j] = 1 if not directed and i != j: - D[j,i] = 1 + D[j, i] = 1 from sage.matrix.constructor import matrix if base_ring is None: base_ring = ZZ M = matrix(base_ring, n, n, D, sparse=sparse, **kwds) return M - am = adjacency_matrix # shorter call makes life easier + am = adjacency_matrix # shorter call makes life easier def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None, *, base_ring=None, **kwds): @@ -2277,13 +2285,13 @@ def distance_matrix(self, vertices=None, *, base_ring=None, **kwds): from sage.matrix.constructor import matrix if ((self.is_directed() and not self.is_strongly_connected()) or - (not self.is_directed() and not self.is_connected())): + (not self.is_directed() and not self.is_connected())): raise ValueError("input (di)graph must be (strongly) connected") if vertices is None: vertices = self.vertices(sort=True) elif (len(vertices) != self.order() or - set(vertices) != set(self.vertex_iterator())): + set(vertices) != set(self.vertex_iterator())): raise ValueError("``vertices`` must be a permutation of the vertices") # We extract from **kwds the arguments for distance_all_pairs @@ -2396,20 +2404,20 @@ def weighted_adjacency_matrix(self, sparse=True, vertices=None, *, base_ring=Non set(vertices) != set(self.vertex_iterator())): raise ValueError("``vertices`` must be a permutation of the vertices") - new_indices = {v: i for i,v in enumerate(vertices)} + new_indices = {v: i for i, v in enumerate(vertices)} D = {} if self._directed: for u, v, l in self.edge_iterator(): i = new_indices[u] j = new_indices[v] - D[i,j] = l + D[i, j] = l else: for u, v, l in self.edge_iterator(): i = new_indices[u] j = new_indices[v] - D[i,j] = l - D[j,i] = l + D[i, j] = l + D[j, i] = l from sage.matrix.constructor import matrix if base_ring is None: M = matrix(self.num_verts(), D, sparse=sparse, **kwds) @@ -2572,29 +2580,28 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl if M.is_sparse(): row_sums = {} if indegree: - for (i,j), entry in M.dict().items(): + for (i, j), entry in M.dict().items(): row_sums[j] = row_sums.get(j, 0) + entry else: - for (i,j), entry in M.dict().items(): + for (i, j), entry in M.dict().items(): row_sums[i] = row_sums.get(i, 0) + entry - for i in range(M.nrows()): - D[i,i] += row_sums.get(i, 0) + D[i, i] += row_sums.get(i, 0) else: if indegree: col_sums = [sum(v) for v in M.columns()] for i in range(M.nrows()): - D[i,i] += col_sums[i] + D[i, i] += col_sums[i] else: - row_sums=[sum(v) for v in M.rows()] + row_sums = [sum(v) for v in M.rows()] for i in range(M.nrows()): - D[i,i] += row_sums[i] + D[i, i] += row_sums[i] if normalized: from sage.misc.functional import sqrt - Dsqrt = diagonal_matrix([1 / sqrt(D[i,i]) if D[i,i] else 1 \ + Dsqrt = diagonal_matrix([1 / sqrt(D[i, i]) if D[i, i] else 1 for i in range(D.nrows())]) if signless: ret = Dsqrt * (D + M) * Dsqrt @@ -2612,7 +2619,7 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl laplacian_matrix = kirchhoff_matrix - ### Attributes + # Attributes def set_embedding(self, embedding): """ @@ -2639,8 +2646,14 @@ def set_embedding(self, embedding): EXAMPLES:: sage: G = graphs.PetersenGraph() - sage: G.set_embedding({0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]}) - sage: G.set_embedding({'s': [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]}) + sage: G.set_embedding({0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], + ....: 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], + ....: 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], + ....: 9: [4, 6, 7]}) + sage: G.set_embedding({'s': [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], + ....: 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], + ....: 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], + ....: 9: [4, 6, 7]}) Traceback (most recent call last): ... ValueError: vertices in ['s'] from the embedding do not belong to the graph @@ -2677,7 +2690,9 @@ def get_embedding(self): sage: G.genus() 1 sage: G.get_embedding() - {0: [1, 4, 5], 1: [0, 2, 6], 2: [1, 3, 7], 3: [2, 4, 8], 4: [0, 3, 9], 5: [0, 7, 8], 6: [1, 9, 8], 7: [2, 5, 9], 8: [3, 6, 5], 9: [4, 6, 7]} + {0: [1, 4, 5], 1: [0, 2, 6], 2: [1, 3, 7], 3: [2, 4, 8], + 4: [0, 3, 9], 5: [0, 7, 8], 6: [1, 9, 8], 7: [2, 5, 9], + 8: [3, 6, 5], 9: [4, 6, 7]} Note that the embeddings gets properly modified on vertex or edge deletion:: @@ -2746,7 +2761,9 @@ def _check_embedding_validity(self, embedding=None, boolean=True): EXAMPLES:: - sage: d = {0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], 8: [3, 5, 6], 9: [4, 6, 7]} + sage: d = {0: [1, 5, 4], 1: [0, 2, 6], 2: [1, 3, 7], 3: [8, 2, 4], + ....: 4: [0, 9, 3], 5: [0, 8, 7], 6: [8, 1, 9], 7: [9, 2, 5], + ....: 8: [3, 5, 6], 9: [4, 6, 7]} sage: G = graphs.PetersenGraph() sage: G._check_embedding_validity(d) True @@ -2794,28 +2811,36 @@ def _check_embedding_validity(self, embedding=None, boolean=True): if boolean: return False if set(embedding).difference(self): - raise ValueError("vertices in {} from the embedding do not belong to the graph".format(list(set(embedding).difference(self)))) + raise ValueError("vertices in {} from the embedding do not " + "belong to the graph".format(list(set(embedding).difference(self)))) else: - raise ValueError("vertices in {} have no corresponding entry in the embedding".format(list(set(self).difference(embedding)))) + raise ValueError("vertices in {} have no corresponding entry " + "in the embedding".format(list(set(self).difference(embedding)))) if self._directed: - connected = lambda u, v: self.has_edge(u, v) or self.has_edge(v, u) + def connected(u, v): + return self.has_edge(u, v) or self.has_edge(v, u) else: connected = self.has_edge for v in embedding: if len(embedding[v]) != self.degree(v): if boolean: return False - raise ValueError("the list associated with vertex {} has length {} but d({})={}".format(v, len(embedding[v]), v, self.degree(v))) + raise ValueError("the list associated with vertex {} has " + "length {} but d({})={}".format(v, len(embedding[v]), + v, self.degree(v))) if len(embedding[v]) != len(set(embedding[v])): if boolean: return False - raise ValueError("the list associated with vertex {} contains >1 occurrences of {}".format(v, [x for x in set(embedding[v]) if embedding[v].count(x) > 1])) + raise ValueError("the list associated with vertex {} contains >1 " + "occurrences of {}".format(v, [x for x in set(embedding[v]) + if embedding[v].count(x) > 1])) for u in embedding[v]: if not connected(v, u): if boolean: return False - raise ValueError("{} and {} are not neighbors but {} is in the list associated with {}".format(u, v, u, v)) + raise ValueError("{} and {} are not neighbors but {} is in " + "the list associated with {}".format(u, v, u, v)) return True def has_loops(self): @@ -23125,7 +23150,6 @@ def is_hamiltonian(self, solver=None, constraint_generation=None, except EmptySetError: return False - def is_isomorphic(self, other, certificate=False, verbosity=0, edge_labels=False): r""" Tests for isomorphism between self and other. From 70486e6108fb53b35c7b094e43e4b71e67806300 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 22 Aug 2022 16:54:35 +0900 Subject: [PATCH 142/414] Refactoring the implementation of q_pochhammer and euler. --- src/sage/rings/lazy_series.py | 87 +++++------------------ src/sage/rings/lazy_series_ring.py | 106 +++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 70 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index c9d377ea596..5c268b33ae5 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1995,6 +1995,10 @@ def q_pochhammer(self, q=None): This is also one version of the quantum dilogarithm or the `q`-Exponential function. + .. SEEALSO:: + + :meth:`sage.rings.lazy_series_ring.LazyLaurentSeriesRing.q_pochhammer` + INPUT: - ``q`` -- (default: `q \in \QQ(q)`) the parameter `q` @@ -2003,55 +2007,16 @@ def q_pochhammer(self, q=None): sage: q = ZZ['q'].fraction_field().gen() sage: L. = LazyLaurentSeriesRing(q.parent()) - sage: qpoch = z.q_pochhammer(q) - sage: qpoch - 1 - + (-1/(-q + 1))*z - + (q/(q^3 - q^2 - q + 1))*z^2 - + (-q^3/(-q^6 + q^5 + q^4 - q^2 - q + 1))*z^3 - + (q^6/(q^10 - q^9 - q^8 + 2*q^5 - q^2 - q + 1))*z^4 - + (-q^10/(-q^15 + q^14 + q^13 - q^10 - q^9 - q^8 + q^7 + q^6 + q^5 - q^2 - q + 1))*z^5 - + (q^15/(q^21 - q^20 - q^19 + q^16 + 2*q^14 - q^12 - q^11 - q^10 - q^9 + 2*q^7 + q^5 - q^2 - q + 1))*z^6 - + O(z^7) - - We show that `(z; q)_n = \frac{(z; q)_{\infty}}{(q^n z; q)_{\infty}}`:: - - sage: qpoch / qpoch(q*z) - 1 - z + O(z^7) - sage: qpoch / qpoch(q^2*z) - 1 + (-q - 1)*z + q*z^2 + O(z^7) - sage: qpoch / qpoch(q^3*z) - 1 + (-q^2 - q - 1)*z + (q^3 + q^2 + q)*z^2 - q^3*z^3 + O(z^7) - sage: qpoch / qpoch(q^4*z) - 1 + (-q^3 - q^2 - q - 1)*z + (q^5 + q^4 + 2*q^3 + q^2 + q)*z^2 - + (-q^6 - q^5 - q^4 - q^3)*z^3 + q^6*z^4 + O(z^7) - - We can also construct part of Euler's function:: - - sage: M. = LazyLaurentSeriesRing(QQ) - sage: phi = sum(qpoch[i](q=a)*a^i for i in range(10)) - sage: phi[:20] == a.euler()[:20] - True - - REFERENCES: - - - :wikipedia:`Q-Pochhammer_symbol` - - :wikipedia:`Quantum_dilogarithm` - - :wikipedia:`Q-exponential` + sage: qp = L.q_pochhammer(q) + sage: (z + z^2).q_pochhammer(q) - qp(z + z^2) + O(z^7) """ - if q is None: - q = ZZ['q'].fraction_field().gen() from .lazy_series_ring import LazyLaurentSeriesRing - from sage.arith.misc import binomial - qP = q.parent() - L = LazyLaurentSeriesRing(qP, "z", sparse=self.parent()._sparse) - one = qP.one() - def coeff(n): - return (-1)**n * q**binomial(n, 2) / qP.prod(one - q**i for i in range(1, n+1)) - f = L(coeff, valuation=0) + P = LazyLaurentSeriesRing(self.base_ring(), "z", sparse=self.parent()._sparse) + f = P.q_pochhammer(q) return f(self) - def euler(self, q=None): + def euler(self): r""" Return the Euler function evaluated at ``self``. @@ -2062,38 +2027,20 @@ def euler(self, q=None): \phi(z) = (z; z)_{\infty} = \sum_{n=0}^{\infty} (-1)^n q^{(3n^2-n)/2}. - INPUT: + .. SEEALSO:: - - ``q`` -- (default: `q \in \QQ(q)`) the parameter `q` + :meth:`sage.rings.lazy_series_ring.LazyLaurentSeriesRing.euler` EXAMPLES:: sage: L. = LazyLaurentSeriesRing(ZZ) - sage: phi = q.euler() - sage: phi - 1 - q - q^2 + q^5 + O(q^7) - - We verify that `1 / phi` gives the generating function - for all partitions:: - - sage: P = 1 / phi; P - 1 + q + 2*q^2 + 3*q^3 + 5*q^4 + 7*q^5 + 11*q^6 + O(q^7) - sage: P[:20] == [Partitions(n).cardinality() for n in range(20)] - True - - REFERENCES: - - - :wikipedia:`Euler_function` + sage: phi = L.euler() + sage: (q + q^2).euler() - phi(q + q^2) + O(q^7) """ from .lazy_series_ring import LazyLaurentSeriesRing - L = LazyLaurentSeriesRing(ZZ, "q", sparse=self.parent()._sparse) - def coeff(n): - k = ZZ(24 * n + 1) - m, rem = k.sqrtrem() - if rem: - return ZZ.zero() - return (-1) ** ((m + 1) // 6) - phi = L(coeff, valuation=0) + P = LazyLaurentSeriesRing(self.base_ring(), "z", sparse=self.parent()._sparse) + phi = P.euler() return phi(self) # === powers === diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 2626384097c..52840fdf30b 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1112,6 +1112,112 @@ def _monomial(self, c, n): """ return self._laurent_poly_ring(c).shift(n) + # === special functions === + + + def q_pochhammer(self, q=None): + r""" + Return the infinite ``q``-Pochhammer symbol `(a; q)_{\infty}`, + where `a` is the variable of ``self``. + + This is also one version of the quantum dilogarithm or + the `q`-Exponential function. + + INPUT: + + - ``q`` -- (default: `q \in \QQ(q)`) the parameter `q` + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: L. = LazyLaurentSeriesRing(q.parent()) + sage: qpoch = L.q_pochhammer(q) + sage: qpoch + 1 + + (-1/(-q + 1))*z + + (q/(q^3 - q^2 - q + 1))*z^2 + + (-q^3/(-q^6 + q^5 + q^4 - q^2 - q + 1))*z^3 + + (q^6/(q^10 - q^9 - q^8 + 2*q^5 - q^2 - q + 1))*z^4 + + (-q^10/(-q^15 + q^14 + q^13 - q^10 - q^9 - q^8 + q^7 + q^6 + q^5 - q^2 - q + 1))*z^5 + + (q^15/(q^21 - q^20 - q^19 + q^16 + 2*q^14 - q^12 - q^11 - q^10 - q^9 + 2*q^7 + q^5 - q^2 - q + 1))*z^6 + + O(z^7) + + We show that `(z; q)_n = \frac{(z; q)_{\infty}}{(q^n z; q)_{\infty}}`:: + + sage: qpoch / qpoch(q*z) + 1 - z + O(z^7) + sage: qpoch / qpoch(q^2*z) + 1 + (-q - 1)*z + q*z^2 + O(z^7) + sage: qpoch / qpoch(q^3*z) + 1 + (-q^2 - q - 1)*z + (q^3 + q^2 + q)*z^2 - q^3*z^3 + O(z^7) + sage: qpoch / qpoch(q^4*z) + 1 + (-q^3 - q^2 - q - 1)*z + (q^5 + q^4 + 2*q^3 + q^2 + q)*z^2 + + (-q^6 - q^5 - q^4 - q^3)*z^3 + q^6*z^4 + O(z^7) + + We can also construct part of Euler's function:: + + sage: M. = LazyLaurentSeriesRing(QQ) + sage: phi = sum(qpoch[i](q=a)*a^i for i in range(10)) + sage: phi[:20] == M.euler()[:20] + True + + REFERENCES: + + - :wikipedia:`Q-Pochhammer_symbol` + - :wikipedia:`Quantum_dilogarithm` + - :wikipedia:`Q-exponential` + """ + if q is None: + q = ZZ['q'].fraction_field().gen() + if q not in self.base_ring(): + raise ValueError("q must be in the base ring") + from .lazy_series_ring import LazyLaurentSeriesRing + from sage.arith.misc import binomial + qP = q.parent() + one = qP.one() + def coeff(n): + return (-1)**n * q**binomial(n, 2) / qP.prod(one - q**i for i in range(1, n+1)) + return self(coeff, valuation=0) + + def euler(self): + r""" + Return the Euler function as an element of ``self``. + + The *Euler function* is defined as + + .. MATH:: + + \phi(z) = (z; z)_{\infty} + = \sum_{n=0}^{\infty} (-1)^n q^{(3n^2-n)/2}. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: phi = q.euler() + sage: phi + 1 - q - q^2 + q^5 + O(q^7) + + We verify that `1 / phi` gives the generating function + for all partitions:: + + sage: P = 1 / phi; P + 1 + q + 2*q^2 + 3*q^3 + 5*q^4 + 7*q^5 + 11*q^6 + O(q^7) + sage: P[:20] == [Partitions(n).cardinality() for n in range(20)] + True + + REFERENCES: + + - :wikipedia:`Euler_function` + """ + from .lazy_series_ring import LazyLaurentSeriesRing + def coeff(n): + k = ZZ(24 * n + 1) + m, rem = k.sqrtrem() + if rem: + return ZZ.zero() + return (-1) ** ((m + 1) // 6) + return self(coeff, valuation=0) + ###################################################################### class LazyTaylorSeriesRing(LazySeriesRing): From 7fb2924620ec1253b32d55952e48eaed7b5ec058 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 23 Aug 2022 17:18:05 -0400 Subject: [PATCH 143/414] correcting shm_managers INPUT and TestSuite --- src/sage/algebras/fusion_rings/f_matrix.py | 3 +- .../algebras/fusion_rings/shm_managers.pyx | 34 +++++++++++-------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 3c24c942dd0..f2c2ab7656a 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -280,7 +280,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab EXAMPLES:: sage: f = FMatrix(FusionRing("B3",2)) - sage: TestSuite(f).run() + sage: TestSuite(f).run(skip="_test_pickling") """ self._FR = fusion_ring if inject_variables and (self._FR._fusion_labels is None): @@ -2476,4 +2476,3 @@ def certify_pentagons(self,use_mp=True, verbose=False): print("Something went wrong. Pentagons remain.") self._fvars = fvars_copy return pe - diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 21a9d0ea3eb..0106adbb59d 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -58,10 +58,10 @@ cdef class KSHandler: must import the ``multiprocessing.shared_memory`` module. Attempting to initialize when ``multiprocessing.shared_memory`` is not available results in an ``ImportError``. - - ``name`` -- the name of a shared memory object - (used by child processes for attaching) - ``init_data`` -- a dictionary or :class:`KSHandler` object containing known squares for initialization, e.g., from a solver checkpoint + - ``name`` -- the name of a shared memory object (used by child processes + for attaching) .. NOTE:: @@ -397,25 +397,29 @@ cdef class FvarsHandler: ``name`` attribute. Children processes use the ``name`` attribute, accessed via ``self.shm.name`` to attach to the shared memory block. + Multiprocessing requires Python 3.8+, since we must import the + ``multiprocessing.shared_memory`` module. Attempting to initialize + when ``multiprocessing.shared_memory`` is not available results in + an ``ImportError``. + INPUT: - - ``factory`` -- an F-matrix - - ``name`` -- the name of a shared memory object - (used by child processes for attaching) - - ``max_terms`` -- maximum number of terms in each entry; since - we use contiguous C-style memory blocks, the size of the block - must be known in advance + - ``n_slots`` -- number of generators of the underlying polynomial ring + - ``field`` -- base field for polynomial ring + - ``idx_to_sextuple`` -- map relating a single integer index to a sextuple + of ``FusionRing`` elements + - ``init_data`` -- a dictionary or :class:`FvarsHandler` object containing + known squares for initialization, e.g., from a solver checkpoint - ``use_mp`` -- an integer indicating the number of child processes used for multiprocessing; if running serially, use 0. - - Multiprocessing requires Python 3.8+, since we must import the - ``multiprocessing.shared_memory`` module. Attempting to initialize - when ``multiprocessing.shared_memory`` is not available results in - an ``ImportError``. - - ``pids_name`` -- the name of a ``ShareableList`` contaning the process ``pid``'s for every process in the pool (including the parent process) + - ``name`` -- the name of a shared memory object + (used by child processes for attaching) + - ``max_terms`` -- maximum number of terms in each entry; since + we use contiguous C-style memory blocks, the size of the block + must be known in advance - ``n_bytes`` -- the number of bytes that should be allocated for each numerator and each denominator stored by the structure @@ -485,7 +489,7 @@ cdef class FvarsHandler: ....: n_proc = 0 ....: pids_name = None sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) - sage: TestSuite(fvars).run() + sage: TestSuite(fvars).run(skip="_test_pickling") sage: if is_shared_memory_available: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ From 61d3a666861bc9518f161aa461fe30be90496c47 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 25 Aug 2022 22:06:50 -0400 Subject: [PATCH 144/414] removing 3.8 mentions in code --- src/sage/algebras/fusion_rings/f_matrix.py | 26 +++++-------------- src/sage/algebras/fusion_rings/fusion_ring.py | 4 +-- .../algebras/fusion_rings/shm_managers.pyx | 11 +------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index f2c2ab7656a..fccb79d4880 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -12,17 +12,13 @@ # **************************************************************************** -#Import pickle for checkpointing and loading -try: - import cPickle as pickle -except: - import pickle from copy import deepcopy from ctypes import cast, py_object from itertools import product, zip_longest -from multiprocessing import Pool, cpu_count, set_start_method +from multiprocessing import Pool, cpu_count, set_start_method, shared_memory import numpy as np from os import getpid, remove +import pickle from sage.algebras.fusion_rings.fast_parallel_fmats_methods import ( _backward_subs, _solve_for_linear_terms, @@ -35,8 +31,7 @@ poly_to_tup, _tup_to_poly, tup_to_univ_poly, _unflatten_coeffs, poly_tup_sortkey, - resize, - # tup_fixes_sq + resize ) from sage.algebras.fusion_rings.shm_managers import KSHandler, FvarsHandler from sage.graphs.graph import Graph @@ -1258,7 +1253,7 @@ def start_worker_pool(self, processes=None): EXAMPLES:: sage: f = FMatrix(FusionRing("G2", 1)) - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: f.start_worker_pool() sage: he = f.get_defining_equations('hexagons') sage: sorted(he) [fx0 - 1, @@ -1282,12 +1277,6 @@ class methods. leak, since shared memory resources outlive the process that created them. """ - #Try to import module requiring Python 3.8+ locally - try: - from multiprocessing import shared_memory - except ImportError: - self.pool = None - return False try: set_start_method('fork') except RuntimeError: @@ -1382,11 +1371,8 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, sage: f._reset_solver_state() sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1,False)])) 11 - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: if is_shared_memory_available: - ....: mp_params = [(i,f.pool._processes,True) for i in range(f.pool._processes)] - ....: else: - ....: mp_params = [(0,1,True)] + sage: f.start_worker_pool() + sage: mp_params = [(i,f.pool._processes,True) for i in range(f.pool._processes)] sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=f.pool,chunksize=1,mp_thresh=0)) 33 sage: f.shutdown_worker_pool() diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index e4a52b4e35a..bd010614493 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -1263,9 +1263,7 @@ def get_braid_generators(self, we don't run the solver again. - ``use_mp`` -- (default: ``True``) a boolean indicating whether to use multiprocessing to speed up the computation; this is - highly recommended. Python 3.8+ is required. This method will - raise an error if it cannot import the necessary components - (``shared_memory`` sub-module of ``multiprocessing``). + highly recommended. Python 3.8+ is required. - ``verbose`` -- (default: ``True``) boolean indicating whether to be verbose with the computation diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 0106adbb59d..5a3eb29694a 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -18,6 +18,7 @@ factory is a cyclotomic field. cimport cython cimport numpy as np from cysignals.memory cimport sig_malloc +from multiprocessing import shared_memory from sage.algebras.fusion_rings.poly_tup_engine cimport poly_to_tup, tup_fixes_sq, _flatten_coeffs from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational @@ -128,11 +129,6 @@ cdef class KSHandler: ]) self.obj_cache = [None]*n if use_mp: - #Try to import module requiring Python 3.8+ locally - try: - from multiprocessing import shared_memory - except ImportError: - raise ImportError("failed to import shared_memory module; requires Python 3.8+") if name is None: self.shm = shared_memory.SharedMemory(create=True, size=n*ks_t.itemsize) else: @@ -509,11 +505,6 @@ cdef class FvarsHandler: self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} self.ngens = n_slots if use_mp: - #Try to import module requiring Python 3.8+ locally - try: - from multiprocessing import shared_memory - except ImportError: - raise ImportError("failed to import shared_memory module; requires Python 3.8+") if name is None: self.shm = shared_memory.SharedMemory(create=True, size=self.ngens*self.fvars_t.itemsize) else: From ff7f5dbd41ef20107f3a3e594ce350ed02f31915 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Thu, 25 Aug 2022 22:49:27 -0400 Subject: [PATCH 145/414] updated doctests to remove back-compatibility with Python 3.8- --- src/sage/algebras/fusion_rings/f_matrix.py | 35 +++--- .../algebras/fusion_rings/shm_managers.pyx | 114 ++++++------------ 2 files changed, 54 insertions(+), 95 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index fccb79d4880..61053cc62af 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -1235,9 +1235,7 @@ def start_worker_pool(self, processes=None): .. NOTE:: Python 3.8+ is required, since the ``multiprocessing.shared_memory`` - module must be imported. If we fail to import the ``shared_memory`` - module, ``self.pool`` is set to ``None``, no worker pool is created, - and all subsequent calculations will run serially. + module must be imported. INPUT: @@ -1314,7 +1312,7 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names, n_proc, pids_name self._pid_list[0] = getpid() for i, p in enumerate(self.pool._pool): self._pid_list[i+1] = p.pid - return True + # return True def shutdown_worker_pool(self): r""" @@ -1331,7 +1329,7 @@ def shutdown_worker_pool(self): EXAMPLES:: sage: f = FMatrix(FusionRing("A1", 3)) - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: f.start_worker_pool() sage: he = f.get_defining_equations('hexagons') sage: f.shutdown_worker_pool() """ @@ -1532,7 +1530,7 @@ def _tup_to_fpoly(self, eq_tup): sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: f = FMatrix(FusionRing("C3", 1)) - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: f.start_worker_pool() sage: he = f.get_defining_equations('hexagons') sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True @@ -1549,17 +1547,13 @@ def _update_reduction_params(self, eqns=None): sage: f = FMatrix(FusionRing("A1", 3)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: f.start_worker_pool() sage: f.get_defining_equations('hexagons', output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f.mp_thresh = 0 - sage: if is_shared_memory_available: - ....: f._fvars = f._shared_fvars - ....: else: - ....: from sage.algebras.fusion_rings.shm_managers import FvarsHandler - ....: f._fvars = FvarsHandler(f._poly_ring.ngens(),f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: f._fvars = f._shared_fvars sage: f._triangular_elim(verbose=False) # indirect doctest sage: f.ideal_basis [] @@ -1760,7 +1754,7 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb sage: f = FMatrix(FusionRing("F4",1)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: f.start_worker_pool() sage: f.get_defining_equations('hexagons',output=False) sage: gb = f._par_graph_gb() Partitioned 10 equations into 2 components of size: @@ -1803,7 +1797,7 @@ def _get_component_variety(self,var,eqns): EXAMPLES:: sage: f = FMatrix(FusionRing("G2", 2)) - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ + sage: f.start_worker_pool() sage: f.get_defining_equations('hexagons', output=False) # long time sage: f.shutdown_worker_pool() sage: partition = f._partition_eqns() # long time @@ -2045,10 +2039,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start - ``use_mp`` -- (default: ``True``) a boolean indicating whether to use multiprocessing to speed up calculation. The default value ``True`` is highly recommended, since parallel processing yields - results much more quickly. Python 3.8+ is required, since the - ``multiprocessing.shared_memory`` module must be imported. If we - fail to import the ``shared_memory`` module, the solver runs - serially. + results much more quickly. - ``verbose`` -- (default: ``True``) a boolean indicating whether the solver should print out intermediate progress reports. @@ -2114,9 +2105,11 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Loading from a pickle with solved F-symbols if self._chkpt_status > 5: return - loads_shared_memory = False + # loads_shared_memory = False + # if use_mp: + # loads_shared_memory = self.start_worker_pool() if use_mp: - loads_shared_memory = self.start_worker_pool() + self.start_worker_pool() if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, self._poly_ring.ngens())) @@ -2129,7 +2122,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) #Unzip _fvars and link to shared_memory structure if using multiprocessing - if use_mp and loads_shared_memory: + if use_mp:# and loads_shared_memory: self._fvars = self._shared_fvars else: n = self._poly_ring.ngens() diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 5a3eb29694a..fd5fe8d0637 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -56,9 +56,7 @@ cdef class KSHandler: - ``field`` -- F-matrix's base cyclotomic field - ``use_mp`` -- a boolean indicating whether to construct a shared memory block to back ``self``. Requires Python 3.8+, since we - must import the ``multiprocessing.shared_memory`` module. Attempting - to initialize when ``multiprocessing.shared_memory`` is not available - results in an ``ImportError``. + must import the ``multiprocessing.shared_memory`` module. - ``init_data`` -- a dictionary or :class:`KSHandler` object containing known squares for initialization, e.g., from a solver checkpoint - ``name`` -- the name of a shared memory object (used by child processes @@ -82,13 +80,11 @@ cdef class KSHandler: creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() - sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available) + sage: f.start_worker_pool() + sage: ks = KSHandler(n,f._field,use_mp=True) sage: #In the same shell or in a different shell, attach to fvars - sage: name = ks.shm.name if is_shared_memory_available else None - sage: ks2 = KSHandler(n,f._field,name=name,use_mp=is_shared_memory_available) - sage: if not is_shared_memory_available: - ....: ks2 = ks + sage: name = ks.shm.name + sage: ks2 = KSHandler(n,f._field,name=name,use_mp=True) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] sage: ks.update([poly_to_tup(p) for p in eqns]) @@ -97,7 +93,7 @@ cdef class KSHandler: ....: Index: 1, square: 4 Index: 3, square: -zeta32^4 + 1/19*zeta32^2 - sage: if is_shared_memory_available: ks.shm.unlink() + sage: ks.shm.unlink() sage: f.shutdown_worker_pool() """ def __init__(self, n_slots, field, use_mp=False, init_data={}, name=None): @@ -112,10 +108,10 @@ cdef class KSHandler: creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() - sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available) + sage: f.start_worker_pool() + sage: ks = KSHandler(n,f._field,use_mp=True) sage: TestSuite(ks).run() - sage: if is_shared_memory_available: ks.shm.unlink() + sage: ks.shm.unlink() sage: f.shutdown_worker_pool() """ cdef int n, d @@ -290,16 +286,14 @@ cdef class KSHandler: sage: f.get_orthogonality_constraints(output=False) sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() - sage: ks = KSHandler(n,f._field,use_mp=is_shared_memory_available,init_data=f._ks) + sage: f.start_worker_pool() + sage: ks = KSHandler(n,f._field,use_mp=True,init_data=f._ks) sage: #In the same shell or in a different one, attach to shared memory handler - sage: name = ks.shm.name if is_shared_memory_available else None - sage: k2 = KSHandler(n,f._field,name=name,use_mp=is_shared_memory_available) - sage: if not is_shared_memory_available: - ....: k2 = ks + sage: name = ks.shm.name + sage: k2 = KSHandler(n,f._field,name=name,use_mp=True) sage: ks == k2 True - sage: if is_shared_memory_available: ks.shm.unlink() + sage: ks.shm.unlink() sage: f.shutdown_worker_pool() """ return all(other.get(idx) == sq for idx, sq in self.items()) @@ -394,9 +388,7 @@ cdef class FvarsHandler: accessed via ``self.shm.name`` to attach to the shared memory block. Multiprocessing requires Python 3.8+, since we must import the - ``multiprocessing.shared_memory`` module. Attempting to initialize - when ``multiprocessing.shared_memory`` is not available results in - an ``ImportError``. + ``multiprocessing.shared_memory`` module. INPUT: @@ -444,25 +436,19 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: if is_shared_memory_available: - ....: n_proc = f.pool._processes - ....: pids_name = f._pid_list.shm.name - ....: else: - ....: n_proc = 0 - ....: pids_name = None + sage: f.start_worker_pool() + sage: n_proc = f.pool._processes + sage: pids_name = f._pid_list.shm.name sage: fvars = FvarsHandler(8, f._field, f._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name) sage: #In the same shell or in a different shell, attach to fvars - sage: name = fvars.shm.name if is_shared_memory_available else None + sage: name = fvars.shm.name sage: fvars2 = FvarsHandler(8, f._field, f._idx_to_sextuple, name=name ,use_mp=n_proc, pids_name=pids_name) - sage: if not is_shared_memory_available: - ....: fvars2 = fvars sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) sage: fvars[f2, f1, f2, f2, f0, f0] = rhs sage: f._tup_to_fpoly(fvars2[f2, f1, f2, f2, f0, f0]) fx5^5 - sage: if is_shared_memory_available: fvars.shm.unlink() + sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, @@ -477,16 +463,12 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: if is_shared_memory_available: - ....: n_proc = f.pool._processes - ....: pids_name = f._pid_list.shm.name - ....: else: - ....: n_proc = 0 - ....: pids_name = None + sage: f.start_worker_pool() + sage: n_proc = f.pool._processes + sage: pids_name = f._pid_list.shm.name sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) sage: TestSuite(fvars).run(skip="_test_pickling") - sage: if is_shared_memory_available: fvars.shm.unlink() + sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ self.field = field @@ -549,13 +531,9 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("B7", 1), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 - sage: is_shared_memory_available = f.start_worker_pool() # Requires Python 3.8+ - sage: if is_shared_memory_available: - ....: n_proc = f.pool._processes - ....: pids_name = f._pid_list.shm.name - ....: else: - ....: n_proc = 0 - ....: pids_name = None + sage: f.start_worker_pool() + sage: n_proc = f.pool._processes + sage: pids_name = f._pid_list.shm.name sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) sage: rhs = tuple((exp, tuple(c._coefficients())) ....: for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10)) @@ -571,7 +549,7 @@ cdef class FvarsHandler: True sage: f._tup_to_fpoly(fvars[r]) == -1/19 True - sage: if is_shared_memory_available: fvars.shm.unlink() + sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() .. NOTE:: @@ -655,13 +633,9 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 - sage: is_shared_memory_available = f.start_worker_pool() - sage: if is_shared_memory_available: - ....: n_proc = f.pool._processes - ....: pids_name = f._pid_list.shm.name - ....: else: - ....: n_proc = 0 - ....: pids_name = None + sage: f.start_worker_pool() + sage: n_proc = f.pool._processes + sage: pids_name = f._pid_list.shm.name sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) sage: rhs = tuple((exp, tuple(c._coefficients())) ....: for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10)) @@ -677,7 +651,7 @@ cdef class FvarsHandler: True sage: f._tup_to_fpoly(fvars[r]) == -1/19 True - sage: if is_shared_memory_available: fvars.shm.unlink() + sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ cdef ETuple exp @@ -740,18 +714,14 @@ cdef class FvarsHandler: sage: f = FMatrix(FusionRing("F4",1)) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() - sage: if is_shared_memory_available: - ....: n_proc = f.pool._processes - ....: pids_name = f._pid_list.shm.name - ....: else: - ....: n_proc = 0 - ....: pids_name = None + sage: f.start_worker_pool() + sage: n_proc = f.pool._processes + sage: pids_name = f._pid_list.shm.name sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=n_proc,pids_name=pids_name) sage: for s, fvar in loads(dumps(fvars)).items(): ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: - sage: if is_shared_memory_available: fvars.shm.unlink() + sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ n = self.fvars.size @@ -792,18 +762,14 @@ def make_FvarsHandler(n,field,idx_map,init_data): sage: f = FMatrix(FusionRing("G2",1)) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: is_shared_memory_available = f.start_worker_pool() - sage: if is_shared_memory_available: - ....: n_proc = f.pool._processes - ....: pids_name = f._pid_list.shm.name - ....: else: - ....: n_proc = 0 - ....: pids_name = None + sage: f.start_worker_pool() + sage: n_proc = f.pool._processes + sage: pids_name = f._pid_list.shm.name sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=n_proc,pids_name=pids_name) sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: - sage: if is_shared_memory_available: fvars.shm.unlink() + sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ return FvarsHandler(n, field, idx_map, init_data=init_data) From c041897a3fb0ad63df2acb39f446e719234bdfa7 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 29 Aug 2022 10:07:31 +0900 Subject: [PATCH 146/414] Trac #34381: Fixing pyflakes issues. --- src/sage/rings/lazy_series_ring.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 52840fdf30b..08ccb377660 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1171,7 +1171,6 @@ def q_pochhammer(self, q=None): q = ZZ['q'].fraction_field().gen() if q not in self.base_ring(): raise ValueError("q must be in the base ring") - from .lazy_series_ring import LazyLaurentSeriesRing from sage.arith.misc import binomial qP = q.parent() one = qP.one() @@ -1209,7 +1208,6 @@ def euler(self): - :wikipedia:`Euler_function` """ - from .lazy_series_ring import LazyLaurentSeriesRing def coeff(n): k = ZZ(24 * n + 1) m, rem = k.sqrtrem() From 66e1f10a4c31be55935f09f517ea4f06e094b0ca Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 2 Sep 2022 12:03:41 -0700 Subject: [PATCH 147/414] build/pkgs/scipy: Update to 1.9.1 --- build/pkgs/scipy/checksums.ini | 6 +++--- build/pkgs/scipy/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/scipy/checksums.ini b/build/pkgs/scipy/checksums.ini index 8090f084328..83f9a385fca 100644 --- a/build/pkgs/scipy/checksums.ini +++ b/build/pkgs/scipy/checksums.ini @@ -1,5 +1,5 @@ tarball=scipy-VERSION.tar.gz -sha1=22c0e73b933b938c272c6eb747cd6b70ab2f9b83 -md5=1f2e527930ddfa15a622b146dae42144 -cksum=994041683 +sha1=2f5a7bb5f992dd6766a3a0e088180be427518903 +md5=e6e70a9014dba74b4ef16686d23fd3ad +cksum=39501961 upstream_url=https://pypi.io/packages/source/s/scipy/scipy-VERSION.tar.gz diff --git a/build/pkgs/scipy/package-version.txt b/build/pkgs/scipy/package-version.txt index f8e233b2733..9ab8337f396 100644 --- a/build/pkgs/scipy/package-version.txt +++ b/build/pkgs/scipy/package-version.txt @@ -1 +1 @@ -1.9.0 +1.9.1 From b85c2d690f6f1e9fce7e91d8d3a44128938d2c9f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 2 Sep 2022 12:04:42 -0700 Subject: [PATCH 148/414] build/pkgs/meson: Update to 0.63.1 --- build/pkgs/meson/checksums.ini | 6 +++--- build/pkgs/meson/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/meson/checksums.ini b/build/pkgs/meson/checksums.ini index c46bd7bb761..c14db7a0114 100644 --- a/build/pkgs/meson/checksums.ini +++ b/build/pkgs/meson/checksums.ini @@ -1,5 +1,5 @@ tarball=meson-VERSION.tar.gz -sha1=85b1c71b598686e40632b5a57ac7f2bdd1dcd926 -md5=d9e7d69f73f875004fbb3cc8bfe2a39b -cksum=339088378 +sha1=e81d2915173324476693e585a785abed8fd1bbad +md5=078e59d11a72b74c3bd78cb8205e9ed7 +cksum=4240866935 upstream_url=https://pypi.io/packages/source/m/meson/meson-VERSION.tar.gz diff --git a/build/pkgs/meson/package-version.txt b/build/pkgs/meson/package-version.txt index 70cd2261d5c..630f2e0ce67 100644 --- a/build/pkgs/meson/package-version.txt +++ b/build/pkgs/meson/package-version.txt @@ -1 +1 @@ -0.63.0 +0.63.1 From a2d8f2f35f529fe708aeafc41b3b73625277eeed Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 3 Sep 2022 12:39:12 -0700 Subject: [PATCH 149/414] build/pkgs/scipy/spkg-check.in: New --- build/pkgs/scipy/spkg-check.in | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 build/pkgs/scipy/spkg-check.in diff --git a/build/pkgs/scipy/spkg-check.in b/build/pkgs/scipy/spkg-check.in new file mode 100644 index 00000000000..0787989fa5e --- /dev/null +++ b/build/pkgs/scipy/spkg-check.in @@ -0,0 +1,3 @@ +python3 -c 'import scipy' +cd src +python3 runtests.py --no-build From aff3fcaf34e789eeef49b887576286dc66676464 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 4 Sep 2022 06:49:31 -0700 Subject: [PATCH 150/414] .github/workflows/docker.yml: Lowercase for DOCKER_PUSH_REPOSITORY --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 23cab40539d..f6456b536c4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -171,7 +171,7 @@ jobs: TOKEN="${{ secrets.GITHUB_TOKEN }}" fi if echo "$TOKEN" | docker login ghcr.io -u ${{ github.actor }} --password-stdin; then - echo "DOCKER_PUSH_REPOSITORY=${{ inputs.docker_push_repository }}" >> $GITHUB_ENV + echo "DOCKER_PUSH_REPOSITORY=$(echo ${{ inputs.docker_push_repository }} | tr "[:upper:]" "[:lower:]")" >> $GITHUB_ENV echo "DOCKER_CONFIG_FILE=$HOME/.docker/config.json" >> $GITHUB_ENV fi # From the docker documentation via .ci/update-env.sh: From a10bd2965e4dfdcbdb4f25d77932330ac981a80a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 4 Sep 2022 11:03:44 -0700 Subject: [PATCH 151/414] build/pkgs/scipy/spkg-check.in: Use scipy.test() --- build/pkgs/scipy/spkg-check.in | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build/pkgs/scipy/spkg-check.in b/build/pkgs/scipy/spkg-check.in index 0787989fa5e..8ab92278cf2 100644 --- a/build/pkgs/scipy/spkg-check.in +++ b/build/pkgs/scipy/spkg-check.in @@ -1,3 +1 @@ -python3 -c 'import scipy' -cd src -python3 runtests.py --no-build +python3 -c 'import scipy; scipy.test()' From 074984448b625aaab4b334732a481d52bdcf3c0f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 4 Sep 2022 11:43:00 -0700 Subject: [PATCH 152/414] build/pkgs/scipy/dependencies_check: New - need pytest --- build/pkgs/scipy/dependencies_check | 1 + 1 file changed, 1 insertion(+) create mode 100644 build/pkgs/scipy/dependencies_check diff --git a/build/pkgs/scipy/dependencies_check b/build/pkgs/scipy/dependencies_check new file mode 100644 index 00000000000..e079f8a6038 --- /dev/null +++ b/build/pkgs/scipy/dependencies_check @@ -0,0 +1 @@ +pytest From c0690f019e4e493d2d8f750d2bc5e7a7d2559d43 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Sep 2022 10:35:41 -0700 Subject: [PATCH 153/414] .github/workflows/docker.yml: Increase fetch_depth --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ebc1a528fe6..6ccd9a07f62 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -135,7 +135,7 @@ jobs: with: repository: ${{ inputs.sage_repo }} ref: ${{ inputs.sage_ref }} - fetch-depth: 2000 + fetch-depth: 10000 - name: fetch tags run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: free disk space From 433b2e9658be0a42e3382a905d79a225cb7f968d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Sep 2022 10:45:56 -0700 Subject: [PATCH 154/414] build/pkgs/meson_python: Add spkg-check.in, add dependencies_check packages: gitpython, pytest_mock --- build/pkgs/gitpython/SPKG.rst | 18 ++++++++++++++++++ build/pkgs/gitpython/dependencies | 4 ++++ build/pkgs/gitpython/requirements.txt | 1 + build/pkgs/gitpython/type | 1 + build/pkgs/meson_python/dependencies_check | 1 + build/pkgs/meson_python/spkg-check.in | 2 ++ build/pkgs/pytest_mock/SPKG.rst | 18 ++++++++++++++++++ build/pkgs/pytest_mock/dependencies | 4 ++++ build/pkgs/pytest_mock/requirements.txt | 1 + build/pkgs/pytest_mock/type | 1 + 10 files changed, 51 insertions(+) create mode 100644 build/pkgs/gitpython/SPKG.rst create mode 100644 build/pkgs/gitpython/dependencies create mode 100644 build/pkgs/gitpython/requirements.txt create mode 100644 build/pkgs/gitpython/type create mode 100644 build/pkgs/meson_python/dependencies_check create mode 100644 build/pkgs/meson_python/spkg-check.in create mode 100644 build/pkgs/pytest_mock/SPKG.rst create mode 100644 build/pkgs/pytest_mock/dependencies create mode 100644 build/pkgs/pytest_mock/requirements.txt create mode 100644 build/pkgs/pytest_mock/type diff --git a/build/pkgs/gitpython/SPKG.rst b/build/pkgs/gitpython/SPKG.rst new file mode 100644 index 00000000000..8928ececb8d --- /dev/null +++ b/build/pkgs/gitpython/SPKG.rst @@ -0,0 +1,18 @@ +gitpython: GitPython is a python library used to interact with Git repositories +=============================================================================== + +Description +----------- + +GitPython is a python library used to interact with Git repositories + +License +------- + +BSD + +Upstream Contact +---------------- + +https://pypi.org/project/GitPython/ + diff --git a/build/pkgs/gitpython/dependencies b/build/pkgs/gitpython/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/gitpython/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/gitpython/requirements.txt b/build/pkgs/gitpython/requirements.txt new file mode 100644 index 00000000000..64b1adaeeb4 --- /dev/null +++ b/build/pkgs/gitpython/requirements.txt @@ -0,0 +1 @@ +GitPython diff --git a/build/pkgs/gitpython/type b/build/pkgs/gitpython/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/gitpython/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/meson_python/dependencies_check b/build/pkgs/meson_python/dependencies_check new file mode 100644 index 00000000000..7aa85eab873 --- /dev/null +++ b/build/pkgs/meson_python/dependencies_check @@ -0,0 +1 @@ +pytest pytest_mock gitpython cython auditwheel_or_delocate diff --git a/build/pkgs/meson_python/spkg-check.in b/build/pkgs/meson_python/spkg-check.in new file mode 100644 index 00000000000..8dd1124505a --- /dev/null +++ b/build/pkgs/meson_python/spkg-check.in @@ -0,0 +1,2 @@ +cd src +pytest diff --git a/build/pkgs/pytest_mock/SPKG.rst b/build/pkgs/pytest_mock/SPKG.rst new file mode 100644 index 00000000000..59692594971 --- /dev/null +++ b/build/pkgs/pytest_mock/SPKG.rst @@ -0,0 +1,18 @@ +pytest_mock: Thin-wrapper around the mock package for easier use with pytest +============================================================================ + +Description +----------- + +Thin-wrapper around the mock package for easier use with pytest + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/pytest-mock/ + diff --git a/build/pkgs/pytest_mock/dependencies b/build/pkgs/pytest_mock/dependencies new file mode 100644 index 00000000000..4f0297e40f8 --- /dev/null +++ b/build/pkgs/pytest_mock/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) pytest iniconfig packaging attrs pluggy tomli py pyparsing | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/pytest_mock/requirements.txt b/build/pkgs/pytest_mock/requirements.txt new file mode 100644 index 00000000000..4ef37316e6a --- /dev/null +++ b/build/pkgs/pytest_mock/requirements.txt @@ -0,0 +1 @@ +pytest-mock diff --git a/build/pkgs/pytest_mock/type b/build/pkgs/pytest_mock/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/pytest_mock/type @@ -0,0 +1 @@ +optional From bce99d207802265cdfd4ad9570856e2aaf22bf32 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Sep 2022 11:31:17 -0700 Subject: [PATCH 155/414] build/pkgs/pytest_mock/dependencies: Remove iniconfig added by mistake --- build/pkgs/pytest_mock/dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/pytest_mock/dependencies b/build/pkgs/pytest_mock/dependencies index 4f0297e40f8..37ea60eb442 100644 --- a/build/pkgs/pytest_mock/dependencies +++ b/build/pkgs/pytest_mock/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) pytest iniconfig packaging attrs pluggy tomli py pyparsing | $(PYTHON_TOOLCHAIN) +$(PYTHON) pytest packaging attrs pluggy tomli py pyparsing | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. From 9a566098a112372e028c5aa1a633469fe714b115 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Sep 2022 12:24:49 -0700 Subject: [PATCH 156/414] tox.ini: Add packages factor 'develop' --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0106e693891..504111ea4c2 100644 --- a/tox.ini +++ b/tox.ini @@ -181,6 +181,7 @@ setenv = # What system packages should be installed. Default: All standard packages with spkg-configure. SAGE_PACKAGE_LIST_ARGS=--has-file=spkg-configure.m4 :standard: recommended: EXTRA_SAGE_PACKAGES_3=_recommended $(head -n 1 build/pkgs/_recommended/dependencies) + develop: EXTRA_SAGE_PACKAGES_4=_develop $(head -n 1 build/pkgs/_develop/dependencies) minimal: SAGE_PACKAGE_LIST_ARGS=_prereq maximal: SAGE_PACKAGE_LIST_ARGS=:standard: :optional: conda-environment: SAGE_PACKAGE_LIST_ARGS=_prereq @@ -570,7 +571,7 @@ setenv = # # Resulting EXTRA_SAGE_PACKAGES # - ALL_EXTRA_SAGE_PACKAGES={env:EXTRA_SAGE_PACKAGES_0:} {env:EXTRA_SAGE_PACKAGES_1:} {env:EXTRA_SAGE_PACKAGES_2:} {env:EXTRA_SAGE_PACKAGES_3:} {env:EXTRA_SAGE_PACKAGES:} + ALL_EXTRA_SAGE_PACKAGES={env:EXTRA_SAGE_PACKAGES_0:} {env:EXTRA_SAGE_PACKAGES_1:} {env:EXTRA_SAGE_PACKAGES_2:} {env:EXTRA_SAGE_PACKAGES_3:} {env:EXTRA_SAGE_PACKAGES_4:} {env:EXTRA_SAGE_PACKAGES:} # environment will be skipped if regular expression does not match against the sys.platform string platform = From 7a4c6690d499609137b6ea881f35a8471a87e27e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Sep 2022 13:04:37 -0700 Subject: [PATCH 157/414] build/pkgs/git/distros/arch.txt: New --- build/pkgs/git/distros/arch.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 build/pkgs/git/distros/arch.txt diff --git a/build/pkgs/git/distros/arch.txt b/build/pkgs/git/distros/arch.txt new file mode 100644 index 00000000000..5664e303b5d --- /dev/null +++ b/build/pkgs/git/distros/arch.txt @@ -0,0 +1 @@ +git From 004197d2eb3fec2104e25053b2b90045c61e95d3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 28 Sep 2022 13:31:14 -0700 Subject: [PATCH 158/414] build/pkgs/pyproject_metadata: Update to 0.6.1 --- build/pkgs/pyproject_metadata/checksums.ini | 6 +++--- build/pkgs/pyproject_metadata/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pyproject_metadata/checksums.ini b/build/pkgs/pyproject_metadata/checksums.ini index da299c46588..4fcc0ec49c2 100644 --- a/build/pkgs/pyproject_metadata/checksums.ini +++ b/build/pkgs/pyproject_metadata/checksums.ini @@ -1,5 +1,5 @@ tarball=pyproject-metadata-VERSION.tar.gz -sha1=5421824aa29786bde43f510365c4d035a0614ba5 -md5=85fcbd5d777809ca2217a996e06fe2e0 -cksum=1327227039 +sha1=c2b7679b1e56a341aa00c186c0d1a6bbd7bd5c2c +md5=e13b11cb723da96f8397addddca963cc +cksum=2246727402 upstream_url=https://pypi.io/packages/source/p/pyproject_metadata/pyproject-metadata-VERSION.tar.gz diff --git a/build/pkgs/pyproject_metadata/package-version.txt b/build/pkgs/pyproject_metadata/package-version.txt index 8f0916f768f..ee6cdce3c29 100644 --- a/build/pkgs/pyproject_metadata/package-version.txt +++ b/build/pkgs/pyproject_metadata/package-version.txt @@ -1 +1 @@ -0.5.0 +0.6.1 From 80d1e58693a633bec4564acd048c2d3020bccfd4 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 29 Sep 2022 12:02:21 +0200 Subject: [PATCH 159/414] use that exp is holonomic --- src/sage/rings/lazy_series.py | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 0b1eba2a6a7..e764411d159 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -3129,6 +3129,62 @@ def _floordiv_(self, other): raise TypeError("must be an integral domain") return P(self / other) + # === fast special functions === + + def exp(self): + r""" + Return the exponential series of ``self``. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: exp(z) + 1 + z + 1/2*z^2 + 1/6*z^3 + 1/24*z^4 + 1/120*z^5 + 1/720*z^6 + O(z^7) + sage: exp(z + z^2) + 1 + z + 3/2*z^2 + 7/6*z^3 + 25/24*z^4 + 27/40*z^5 + 331/720*z^6 + O(z^7) + sage: exp(0) + 1 + sage: exp(1 + z) + Traceback (most recent call last): + ... + ValueError: can only compose with a positive valuation series + + sage: L. = LazyPowerSeriesRing(QQ) + sage: exp(x+y)[4].factor() + (1/24) * (x + y)^4 + sage: exp(x/(1-y)).polynomial(3) + 1/6*x^3 + x^2*y + x*y^2 + 1/2*x^2 + x*y + x + 1 + + TESTS:: + + sage: L. = LazyLaurentSeriesRing(QQ); x = var("x") + sage: exp(z)[0:6] == exp(x).series(x, 6).coefficients(sparse=False) + True + + Check the exponential when the base ring is a lazy ring:: + + sage: L. = LazyPowerSeriesRing(QQ) + sage: M. = LazyPowerSeriesRing(L) + sage: exp(x) + 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + O(x^7) + """ + P = self.parent() + R = self.base_ring() + coeff_stream = self._coeff_stream + if any(coeff_stream[i] for i in range(coeff_stream._approximate_order, 1)): + raise ValueError("can only compose with a positive valuation series") + # WARNING: d_self need not be a proper element of P, e.g. for + # multivariate power series + d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], + P.is_sparse(), 0) + f = P.undefined(valuation=0) + d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream) + int_d_self_f = Stream_function(lambda n: d_self_f[n-1] / R(n) if n else R.one(), + P.is_sparse(), 0) + f._coeff_stream._target = int_d_self_f + return f + + class LazyLaurentSeries(LazyCauchyProductSeries): r""" A Laurent series where the coefficients are computed lazily. From 08ca5a3b8b9dac49beacd14efa49883f197d6212 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 29 Sep 2022 16:02:18 +0200 Subject: [PATCH 160/414] provide fast logarithm --- src/sage/rings/lazy_series.py | 113 +++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index e764411d159..a880039b90e 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1809,36 +1809,10 @@ def exp(self): EXAMPLES:: - sage: L. = LazyLaurentSeriesRing(QQ) - sage: exp(z) - 1 + z + 1/2*z^2 + 1/6*z^3 + 1/24*z^4 + 1/120*z^5 + 1/720*z^6 + O(z^7) - sage: exp(z + z^2) - 1 + z + 3/2*z^2 + 7/6*z^3 + 25/24*z^4 + 27/40*z^5 + 331/720*z^6 + O(z^7) - sage: exp(0) - 1 - sage: exp(1 + z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series - - sage: L. = LazyPowerSeriesRing(QQ) - sage: exp(x+y)[4].factor() - (1/24) * (x + y)^4 - sage: exp(x/(1-y)).polynomial(3) - 1/6*x^3 + x^2*y + x*y^2 + 1/2*x^2 + x*y + x + 1 - - TESTS:: - - sage: L. = LazyLaurentSeriesRing(QQ); x = var("x") - sage: exp(z)[0:6] == exp(x).series(x, 6).coefficients(sparse=False) - True - - Check the exponential when the base ring is a lazy ring:: - - sage: L. = LazyPowerSeriesRing(QQ) - sage: M. = LazyPowerSeriesRing(L) - sage: exp(x) - 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + O(x^7) + sage: L = LazyDirichletSeriesRing(QQ, "s") + sage: Z = L(constant=1, valuation=2) + sage: exp(Z) + 1 + 1/(2^s) + 1/(3^s) + 3/2/4^s + 1/(5^s) + 2/6^s + 1/(7^s) + O(1/(8^s)) """ from .lazy_series_ring import LazyLaurentSeriesRing P = LazyLaurentSeriesRing(self.base_ring(), "z", sparse=self.parent()._sparse) @@ -1851,24 +1825,10 @@ def log(self): EXAMPLES:: - sage: L. = LazyLaurentSeriesRing(QQ) - sage: log(1/(1-z)) - z + 1/2*z^2 + 1/3*z^3 + 1/4*z^4 + 1/5*z^5 + 1/6*z^6 + 1/7*z^7 + O(z^8) - - sage: L. = LazyPowerSeriesRing(QQ) - sage: log((1 + x/(1-y))).polynomial(3) - 1/3*x^3 - x^2*y + x*y^2 - 1/2*x^2 + x*y + x - - TESTS:: - - sage: L. = LazyLaurentSeriesRing(QQ); x = var("x") - sage: log(1+z)[0:6] == log(1+x).series(x, 6).coefficients(sparse=False) - True - - sage: log(z) - Traceback (most recent call last): - ... - ValueError: can only compose with a positive valuation series + sage: L = LazyDirichletSeriesRing(QQ, "s") + sage: Z = L(constant=1) + sage: log(Z) + 1/(2^s) + 1/(3^s) + 1/2/4^s + 1/(5^s) + 1/(7^s) + O(1/(8^s)) """ from .lazy_series_ring import LazyLaurentSeriesRing P = LazyLaurentSeriesRing(self.base_ring(), "z", sparse=self.parent()._sparse) @@ -3135,6 +3095,12 @@ def exp(self): r""" Return the exponential series of ``self``. + We use the identity + + .. MATH:: + + \exp(s) = 1 + \int s' \exp(s). + EXAMPLES:: sage: L. = LazyLaurentSeriesRing(QQ) @@ -3171,6 +3137,8 @@ def exp(self): P = self.parent() R = self.base_ring() coeff_stream = self._coeff_stream + # TODO: coefficients should not be checked here, it prevents + # us from using self.define in some cases! if any(coeff_stream[i] for i in range(coeff_stream._approximate_order, 1)): raise ValueError("can only compose with a positive valuation series") # WARNING: d_self need not be a proper element of P, e.g. for @@ -3184,6 +3152,55 @@ def exp(self): f._coeff_stream._target = int_d_self_f return f + def log(self): + r""" + Return the series for the natural logarithm of ``self``. + + We use the identity + + .. MATH:: + + \log(s) = \int s' / s. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: log(1/(1-z)) + z + 1/2*z^2 + 1/3*z^3 + 1/4*z^4 + 1/5*z^5 + 1/6*z^6 + 1/7*z^7 + O(z^8) + + sage: L. = LazyPowerSeriesRing(QQ) + sage: log((1 + x/(1-y))).polynomial(3) + 1/3*x^3 - x^2*y + x*y^2 - 1/2*x^2 + x*y + x + + TESTS:: + + sage: L. = LazyLaurentSeriesRing(QQ); x = var("x") + sage: log(1+z)[0:6] == log(1+x).series(x, 6).coefficients(sparse=False) + True + + sage: log(z) + Traceback (most recent call last): + ... + ValueError: can only compose with a positive valuation series + """ + P = self.parent() + R = self.base_ring() + coeff_stream = self._coeff_stream + # TODO: coefficients should not be checked here, it prevents + # us from using self.define in some cases! + if (any(coeff_stream[i] for i in range(coeff_stream._approximate_order, 0)) + or coeff_stream[0] != R.one()): + raise ValueError("can only compose with a positive valuation series") + # WARNING: d_self need not be a proper element of P, e.g. for + # multivariate power series + d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], + P.is_sparse(), 0) + d_self_quo_self = Stream_cauchy_mul(d_self, + Stream_cauchy_invert(coeff_stream)) + int_d_self_quo_self = Stream_function(lambda n: d_self_quo_self[n-1] / R(n), + P.is_sparse(), 1) + return P.element_class(P, int_d_self_quo_self) + class LazyLaurentSeries(LazyCauchyProductSeries): r""" From d2faec419ae8db9d21f13aca431e37c9d710d0d7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Sep 2022 18:16:01 -0700 Subject: [PATCH 161/414] build/pkgs/meson_python: Update to 0.9.0 --- build/pkgs/meson_python/checksums.ini | 6 +++--- build/pkgs/meson_python/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/meson_python/checksums.ini b/build/pkgs/meson_python/checksums.ini index e72f2a738cc..cc19a05eee3 100644 --- a/build/pkgs/meson_python/checksums.ini +++ b/build/pkgs/meson_python/checksums.ini @@ -1,5 +1,5 @@ tarball=meson_python-VERSION.tar.gz -sha1=18c1742379f1a9f3905c67c348aeb2442b02c119 -md5=38cc212d532a55ba4a53c572656ecd70 -cksum=4204960713 +sha1=87b086c28d626182056d020d8e4eeafa8bb8ccee +md5=4774f7348d5fc6769e62716600d3773a +cksum=3965133995 upstream_url=https://pypi.io/packages/source/m/meson_python/meson_python-VERSION.tar.gz diff --git a/build/pkgs/meson_python/package-version.txt b/build/pkgs/meson_python/package-version.txt index 6f4eebdf6f6..ac39a106c48 100644 --- a/build/pkgs/meson_python/package-version.txt +++ b/build/pkgs/meson_python/package-version.txt @@ -1 +1 @@ -0.8.1 +0.9.0 From 99de8018b1bc3f599b10bea2480aed7417dedc3f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Sep 2022 18:18:16 -0700 Subject: [PATCH 162/414] build/pkgs/meson_python/patches/126.patch: Remove (upstreamed) --- build/pkgs/meson_python/patches/126.patch | 26 ----------------------- 1 file changed, 26 deletions(-) delete mode 100644 build/pkgs/meson_python/patches/126.patch diff --git a/build/pkgs/meson_python/patches/126.patch b/build/pkgs/meson_python/patches/126.patch deleted file mode 100644 index 3f417b41a5e..00000000000 --- a/build/pkgs/meson_python/patches/126.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 320ecea8996a062317ac7790eebe17f5f3f95551 Mon Sep 17 00:00:00 2001 -From: Matthias Koeppe -Date: Tue, 2 Aug 2022 16:20:50 -0700 -Subject: [PATCH] Project.platform_tag: Handle the case of 32bit Python on - x86_64 - ---- - mesonpy/__init__.py | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py -index aff3dd1..bdcda25 100644 ---- a/mesonpy/__init__.py -+++ b/mesonpy/__init__.py -@@ -684,6 +684,11 @@ def platform_tag(self) -> str: - # https://github.com/pypa/packaging/issues/578 - parts[1] += '.0' - platform_ = '-'.join(parts) -+ elif parts[0] == 'linux' and parts[1] == 'x86_64' and sys.maxsize == 2147483647: -+ # 32-bit Python running on an x86_64 host -+ # https://github.com/FFY00/meson-python/issues/123 -+ parts[1] = 'i686' -+ platform_ = '-'.join(parts) - return platform_.replace('-', '_').replace('.', '_') - - def _calculate_file_abi_tag_heuristic_windows(self, filename: str) -> Optional[mesonpy._tags.Tag]: From bd3e3ebf0d7b707b9b03ab42ee7e7c71a28df782 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 7 Oct 2022 18:26:37 +0200 Subject: [PATCH 163/414] make sparsity a decision of the user --- src/sage/data_structures/stream.py | 323 ++++++++++++++--------------- src/sage/rings/lazy_series.py | 51 ++--- src/sage/rings/lazy_series_ring.py | 51 +++-- 3 files changed, 202 insertions(+), 223 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index d8d780dbca4..2bbb2f99106 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -27,33 +27,33 @@ sage: g = Stream_function(lambda n: 1, True, 0) sage: [g[i] for i in range(10)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) sage: [h[i] for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] We can subtract one stream from another:: - sage: h = Stream_sub(f, g) + sage: h = Stream_sub(f, g, True) sage: [h[i] for i in range(10)] [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] There is a Cauchy product on streams:: - sage: h = Stream_cauchy_mul(f, g) + sage: h = Stream_cauchy_mul(f, g, True) sage: [h[i] for i in range(10)] [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] We can compute the inverse corresponding to the Cauchy product:: sage: ginv = Stream_cauchy_invert(g) - sage: h = Stream_cauchy_mul(f, ginv) + sage: h = Stream_cauchy_mul(f, ginv, True) sage: [h[i] for i in range(10)] [0, 1, 1, 1, 1, 1, 1, 1, 1, 1] Two streams can be composed:: sage: g = Stream_function(lambda n: n, True, 1) - sage: h = Stream_cauchy_compose(f, g) + sage: h = Stream_cauchy_compose(f, g, True) sage: [h[i] for i in range(10)] [0, 1, 4, 14, 46, 145, 444, 1331, 3926, 11434] @@ -153,7 +153,7 @@ def _approximate_order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: f = Stream_exact([0,3], True) + sage: f = Stream_exact([0,3]) sage: f._approximate_order 1 """ @@ -286,9 +286,9 @@ def __getstate__(self): sage: from sage.data_structures.stream import Stream_exact sage: from sage.data_structures.stream import Stream_cauchy_mul - sage: h = Stream_exact([1], True) - sage: g = Stream_exact([1, -1, -1], True) - sage: u = Stream_cauchy_mul(h, g) + sage: h = Stream_exact([1]) + sage: g = Stream_exact([1, -1, -1]) + sage: u = Stream_cauchy_mul(h, g, True) sage: [u[i] for i in range(10)] [1, -1, -1, 0, 0, 0, 0, 0, 0, 0] sage: u._cache @@ -299,9 +299,9 @@ def __getstate__(self): sage: [m[i] for i in range(10)] [1, -1, -1, 0, 0, 0, 0, 0, 0, 0] - sage: h = Stream_exact([1], False) - sage: g = Stream_exact([1, -1, -1], False) - sage: u = Stream_cauchy_mul(h, g) + sage: h = Stream_exact([1]) + sage: g = Stream_exact([1, -1, -1]) + sage: u = Stream_cauchy_mul(h, g, False) sage: [u[i] for i in range(10)] [1, -1, -1, 0, 0, 0, 0, 0, 0, 0] sage: u._cache @@ -331,10 +331,10 @@ def __setstate__(self, d): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: h = Stream_exact([-1], True) - sage: g = Stream_exact([1, -1], True) + sage: h = Stream_exact([-1]) + sage: g = Stream_exact([1, -1]) sage: from sage.data_structures.stream import Stream_cauchy_mul - sage: u = Stream_cauchy_mul(h, g) + sage: u = Stream_cauchy_mul(h, g, True) sage: [u[i] for i in range(10)] [-1, 1, 0, 0, 0, 0, 0, 0, 0, 0] sage: loads(dumps(u)) == u @@ -406,7 +406,7 @@ def iterate_coefficients(self): sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose sage: f = Stream_function(lambda n: 1, False, 1) sage: g = Stream_function(lambda n: n^3, False, 1) - sage: h = Stream_cauchy_compose(f, g) + sage: h = Stream_cauchy_compose(f, g, True) sage: n = h.iterate_coefficients() sage: [next(n) for i in range(10)] [1, 9, 44, 207, 991, 4752, 22769, 109089, 522676, 2504295] @@ -585,51 +585,51 @@ class Stream_exact(Stream): the input is shifted to have the prescribed order. """ - def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, order=None): + def __init__(self, initial_coefficients, constant=None, degree=None, order=None): """ Initialize a stream with eventually constant coefficients. TESTS:: sage: from sage.data_structures.stream import Stream_exact - sage: Stream_exact([], False) + sage: Stream_exact([]) Traceback (most recent call last): ... AssertionError: Stream_exact should only be used for non-zero streams - sage: s = Stream_exact([0, 0, 1, 0, 0], False) + sage: s = Stream_exact([0, 0, 1, 0, 0]) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1,), 2, 3, True) - sage: s = Stream_exact([0, 0, 1, 0, 0], False, constant=0) + sage: s = Stream_exact([0, 0, 1, 0, 0], constant=0) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1,), 2, 3, True) - sage: s = Stream_exact([0, 0, 1, 0, 0], False, constant=0, degree=10) + sage: s = Stream_exact([0, 0, 1, 0, 0], constant=0, degree=10) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1,), 2, 3, True) - sage: s = Stream_exact([0, 0, 1, 0, 0], False, constant=1) + sage: s = Stream_exact([0, 0, 1, 0, 0], constant=1) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1,), 2, 5, True) - sage: s = Stream_exact([0, 0, 1, 0, 1], False, constant=1, degree=10) + sage: s = Stream_exact([0, 0, 1, 0, 1], constant=1, degree=10) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1, 0, 1), 2, 10, True) - sage: s = Stream_exact([0, 0, 1, 0, 1], False, constant=1, degree=5) + sage: s = Stream_exact([0, 0, 1, 0, 1], constant=1, degree=5) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1,), 2, 4, True) - sage: s = Stream_exact([0, 0, 1, 2, 0, 1], False, constant=1) + sage: s = Stream_exact([0, 0, 1, 2, 0, 1], constant=1) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1, 2), 2, 5, True) - sage: s = Stream_exact([0, 0, 1, 2, 1, 1], False, constant=1) + sage: s = Stream_exact([0, 0, 1, 2, 1, 1], constant=1) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1, 2), 2, 4, True) - sage: s = Stream_exact([0, 0, 1, 2, 1, 1], False, constant=1, order=-2) + sage: s = Stream_exact([0, 0, 1, 2, 1, 1], constant=1, order=-2) sage: s._initial_coefficients, s._approximate_order, s._degree, s._true_order ((1, 2), 0, 2, True) """ @@ -680,7 +680,7 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, assert self._initial_coefficients or self._constant, "Stream_exact should only be used for non-zero streams" - super().__init__(is_sparse, True) + super().__init__(None, True) self._approximate_order = order def __getitem__(self, n): @@ -694,35 +694,35 @@ def __getitem__(self, n): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([1], False) + sage: s = Stream_exact([1]) sage: [s[i] for i in range(-2, 5)] [0, 0, 1, 0, 0, 0, 0] - sage: s = Stream_exact([], False, constant=1) + sage: s = Stream_exact([], constant=1) sage: [s[i] for i in range(-2, 5)] [0, 0, 1, 1, 1, 1, 1] - sage: s = Stream_exact([2], False, constant=1) + sage: s = Stream_exact([2], constant=1) sage: [s[i] for i in range(-2, 5)] [0, 0, 2, 1, 1, 1, 1] - sage: s = Stream_exact([2], False, order=-1, constant=1) + sage: s = Stream_exact([2], order=-1, constant=1) sage: [s[i] for i in range(-2, 5)] [0, 2, 1, 1, 1, 1, 1] - sage: s = Stream_exact([2], False, order=-1, degree=2, constant=1) + sage: s = Stream_exact([2], order=-1, degree=2, constant=1) sage: [s[i] for i in range(-2, 5)] [0, 2, 0, 0, 1, 1, 1] - sage: t = Stream_exact([0, 2, 0], False, order=-2, degree=2, constant=1) + sage: t = Stream_exact([0, 2, 0], order=-2, degree=2, constant=1) sage: t == s True - sage: s = Stream_exact([0,1,2,1,0,0,1,1], False, constant=1) + sage: s = Stream_exact([0,1,2,1,0,0,1,1], constant=1) sage: [s[i] for i in range(10)] [0, 1, 2, 1, 0, 0, 1, 1, 1, 1] - sage: t = Stream_exact([0,1,2,1,0,0], False, constant=1) + sage: t = Stream_exact([0,1,2,1,0,0], constant=1) sage: s == t True """ @@ -741,7 +741,7 @@ def order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([1], False) + sage: s = Stream_exact([1]) sage: s.order() 0 @@ -755,7 +755,7 @@ def __hash__(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([1], False) + sage: s = Stream_exact([1]) sage: hash(s) == hash(s) True """ @@ -772,16 +772,16 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([2], False, order=-1, degree=2, constant=1) - sage: t = Stream_exact([0, 2, 0], False, 1, 2, -2) + sage: s = Stream_exact([2], order=-1, degree=2, constant=1) + sage: t = Stream_exact([0, 2, 0], 1, 2, -2) sage: [s[i] for i in range(10)] [0, 0, 1, 1, 1, 1, 1, 1, 1, 1] sage: [t[i] for i in range(10)] [0, 0, 1, 1, 1, 1, 1, 1, 1, 1] sage: s == t True - sage: s = Stream_exact([2], False, constant=1) - sage: t = Stream_exact([2], False, order=-1, constant=1) + sage: s = Stream_exact([2], constant=1) + sage: t = Stream_exact([2], order=-1, constant=1) sage: [s[i] for i in range(10)] [2, 1, 1, 1, 1, 1, 1, 1, 1, 1] sage: [t[i] for i in range(10)] @@ -791,8 +791,8 @@ def __eq__(self, other): sage: t == t True - sage: s = Stream_exact([2], False, order=0, degree=5, constant=1) - sage: t = Stream_exact([2], False, order=-1, degree=5, constant=1) + sage: s = Stream_exact([2], order=0, degree=5, constant=1) + sage: t = Stream_exact([2], order=-1, degree=5, constant=1) sage: s == t False """ @@ -814,12 +814,12 @@ def __ne__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([2], False, order=-1, degree=2, constant=1) - sage: t = Stream_exact([0, 2, 0], False, 1, 2, -2) + sage: s = Stream_exact([2], order=-1, degree=2, constant=1) + sage: t = Stream_exact([0, 2, 0], 1, 2, -2) sage: s != t False - sage: s = Stream_exact([2], False, constant=1) - sage: t = Stream_exact([2], False, order=-1, constant=1) + sage: s = Stream_exact([2], constant=1) + sage: t = Stream_exact([2], order=-1, constant=1) sage: s != t True @@ -866,7 +866,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([2], False, order=-1, degree=2, constant=1) + sage: s = Stream_exact([2], order=-1, degree=2, constant=1) sage: s.is_nonzero() True """ @@ -879,7 +879,7 @@ def _polynomial_part(self, R): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: s = Stream_exact([2], False, order=-1, degree=2, constant=1) + sage: s = Stream_exact([2], order=-1, degree=2, constant=1) sage: L. = LazyLaurentSeriesRing(ZZ) sage: s._polynomial_part(L._laurent_poly_ring) 2*z^-1 @@ -987,7 +987,7 @@ class Stream_uninitialized(Stream_inexact): sage: from sage.data_structures.stream import Stream_uninitialized sage: from sage.data_structures.stream import Stream_exact - sage: one = Stream_exact([1], True) + sage: one = Stream_exact([1]) sage: C = Stream_uninitialized(True, 0) sage: C._target sage: C._target = one @@ -1022,7 +1022,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import Stream_uninitialized sage: from sage.data_structures.stream import Stream_exact - sage: one = Stream_exact([1], True) + sage: one = Stream_exact([1]) sage: C = Stream_uninitialized(True, 0) sage: C._target sage: C._target = one @@ -1039,7 +1039,7 @@ def iterate_coefficients(self): sage: from sage.data_structures.stream import Stream_uninitialized sage: from sage.data_structures.stream import Stream_exact - sage: z = Stream_exact([1], True, order=1) + sage: z = Stream_exact([1], order=1) sage: C = Stream_uninitialized(True, 0) sage: C._target sage: C._target = z @@ -1081,7 +1081,7 @@ def __init__(self, series, is_sparse): sage: from sage.data_structures.stream import Stream_unary sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_exact) - sage: f = Stream_exact([1, -1], False) + sage: f = Stream_exact([1, -1]) sage: g = Stream_cauchy_invert(f) sage: isinstance(g, Stream_unary) True @@ -1143,10 +1143,10 @@ class Stream_binary(Stream_inexact): sage: from sage.data_structures.stream import (Stream_function, Stream_add, Stream_sub) sage: f = Stream_function(lambda n: 2*n, True, 0) sage: g = Stream_function(lambda n: n, True, 1) - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] - sage: h = Stream_sub(f, g) + sage: h = Stream_sub(f, g, True) sage: [h[i] for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] """ @@ -1159,11 +1159,11 @@ def __init__(self, left, right, is_sparse): sage: from sage.data_structures.stream import Stream_binary sage: from sage.data_structures.stream import (Stream_add, Stream_cauchy_invert, Stream_exact) - sage: f1 = Stream_exact([1, -1], False) + sage: f1 = Stream_exact([1, -1]) sage: g1 = Stream_cauchy_invert(f1) - sage: f2 = Stream_exact([1, 1], False) + sage: f2 = Stream_exact([1, 1]) sage: g2 = Stream_cauchy_invert(f2) - sage: O = Stream_add(g1, g2) + sage: O = Stream_add(g1, g2, True) sage: isinstance(O, Stream_binary) True sage: TestSuite(O).run() @@ -1202,9 +1202,9 @@ def __eq__(self, other): sage: f = Stream_function(lambda n: 2*n, False, 1) sage: g = Stream_function(lambda n: n, False, 1) sage: h = Stream_function(lambda n: 1, False, 1) - sage: t = Stream_cauchy_mul(f, g) - sage: u = Stream_cauchy_mul(g, h) - sage: v = Stream_cauchy_mul(h, f) + sage: t = Stream_cauchy_mul(f, g, True) + sage: u = Stream_cauchy_mul(g, h, True) + sage: v = Stream_cauchy_mul(h, f, True) sage: t == u False sage: t == t @@ -1226,10 +1226,10 @@ class Stream_binaryCommutative(Stream_binary): sage: from sage.data_structures.stream import (Stream_function, Stream_add) sage: f = Stream_function(lambda n: 2*n, True, 0) sage: g = Stream_function(lambda n: n, True, 1) - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] - sage: u = Stream_add(g, f) + sage: u = Stream_add(g, f, True) sage: [u[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] sage: h == u @@ -1244,8 +1244,8 @@ def __hash__(self): sage: from sage.data_structures.stream import (Stream_function, Stream_add) sage: f = Stream_function(lambda n: 2*n, True, 0) sage: g = Stream_function(lambda n: n, True, 1) - sage: h = Stream_add(f, g) - sage: u = Stream_add(g, f) + sage: h = Stream_add(f, g, True) + sage: u = Stream_add(g, f, True) sage: hash(h) == hash(u) True """ @@ -1264,10 +1264,10 @@ def __eq__(self, other): sage: from sage.data_structures.stream import (Stream_function, Stream_add) sage: f = Stream_function(lambda n: 2*n, True, 0) sage: g = Stream_function(lambda n: n, True, 1) - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] - sage: u = Stream_add(g, f) + sage: u = Stream_add(g, f, True) sage: [u[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] sage: h == u @@ -1396,14 +1396,14 @@ class Stream_add(Stream_binaryCommutative): sage: from sage.data_structures.stream import (Stream_add, Stream_function) sage: f = Stream_function(lambda n: n, True, 0) sage: g = Stream_function(lambda n: 1, True, 0) - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) sage: [h[i] for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - sage: u = Stream_add(g, f) + sage: u = Stream_add(g, f, True) sage: [u[i] for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ - def __init__(self, left, right): + def __init__(self, left, right, is_sparse): """ Initialize ``self``. @@ -1412,11 +1412,9 @@ def __init__(self, left, right): sage: from sage.data_structures.stream import (Stream_function, Stream_add) sage: f = Stream_function(lambda n: 1, True, 0) sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) """ - if left._is_sparse != right._is_sparse: - raise NotImplementedError - super().__init__(left, right, left._is_sparse) + super().__init__(left, right, is_sparse) @lazy_attribute def _approximate_order(self): @@ -1426,7 +1424,7 @@ def _approximate_order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact - sage: h = Stream_exact([0,3], True) + sage: h = Stream_exact([0,3]) sage: h._approximate_order 1 """ @@ -1446,7 +1444,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_function, Stream_add) sage: f = Stream_function(lambda n: n, True, 0) sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) sage: h.get_coefficient(5) 30 sage: [h.get_coefficient(i) for i in range(10)] @@ -1469,14 +1467,14 @@ class Stream_sub(Stream_binary): sage: from sage.data_structures.stream import (Stream_sub, Stream_function) sage: f = Stream_function(lambda n: n, True, 0) sage: g = Stream_function(lambda n: 1, True, 0) - sage: h = Stream_sub(f, g) + sage: h = Stream_sub(f, g, True) sage: [h[i] for i in range(10)] [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] - sage: u = Stream_sub(g, f) + sage: u = Stream_sub(g, f, True) sage: [u[i] for i in range(10)] [1, 0, -1, -2, -3, -4, -5, -6, -7, -8] """ - def __init__(self, left, right): + def __init__(self, left, right, is_sparse): """ initialize ``self``. @@ -1485,11 +1483,9 @@ def __init__(self, left, right): sage: from sage.data_structures.stream import (Stream_function, Stream_sub) sage: f = Stream_function(lambda n: 1, True, 0) sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_sub(f, g) + sage: h = Stream_sub(f, g, True) """ - if left._is_sparse != right._is_sparse: - raise NotImplementedError - super().__init__(left, right, left._is_sparse) + super().__init__(left, right, is_sparse) @lazy_attribute def _approximate_order(self): @@ -1499,9 +1495,9 @@ def _approximate_order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact, Stream_function, Stream_add - sage: f = Stream_exact([0,3], True) + sage: f = Stream_exact([0,3]) sage: g = Stream_function(lambda n: -3*n, True, 1) - sage: h = Stream_add(f, g) + sage: h = Stream_add(f, g, True) sage: h._approximate_order 1 sage: [h[i] for i in range(5)] @@ -1523,7 +1519,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_function, Stream_sub) sage: f = Stream_function(lambda n: n, True, 0) sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_sub(f, g) + sage: h = Stream_sub(f, g, True) sage: h.get_coefficient(5) -20 sage: [h.get_coefficient(i) for i in range(10)] @@ -1550,14 +1546,14 @@ class Stream_cauchy_mul(Stream_binary): sage: from sage.data_structures.stream import (Stream_cauchy_mul, Stream_function) sage: f = Stream_function(lambda n: n, True, 0) sage: g = Stream_function(lambda n: 1, True, 0) - sage: h = Stream_cauchy_mul(f, g) + sage: h = Stream_cauchy_mul(f, g, True) sage: [h[i] for i in range(10)] [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] - sage: u = Stream_cauchy_mul(g, f) + sage: u = Stream_cauchy_mul(g, f, True) sage: [u[i] for i in range(10)] [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] """ - def __init__(self, left, right): + def __init__(self, left, right, is_sparse): """ initialize ``self``. @@ -1566,11 +1562,9 @@ def __init__(self, left, right): sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) sage: f = Stream_function(lambda n: 1, True, 0) sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_cauchy_mul(f, g) + sage: h = Stream_cauchy_mul(f, g, True) """ - if left._is_sparse != right._is_sparse: - raise NotImplementedError - super().__init__(left, right, left._is_sparse) + super().__init__(left, right, is_sparse) @lazy_attribute def _approximate_order(self): @@ -1580,9 +1574,9 @@ def _approximate_order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact, Stream_function, Stream_cauchy_mul - sage: f = Stream_exact([0, Zmod(6)(2)], True) + sage: f = Stream_exact([0, Zmod(6)(2)]) sage: g = Stream_function(lambda n: Zmod(6)(3*n), True, 1) - sage: h = Stream_cauchy_mul(f, g) + sage: h = Stream_cauchy_mul(f, g, True) sage: h._approximate_order 2 sage: [h[i] for i in range(5)] @@ -1604,7 +1598,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) sage: f = Stream_function(lambda n: n, True, 0) sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_cauchy_mul(f, g) + sage: h = Stream_cauchy_mul(f, g, True) sage: h.get_coefficient(5) 50 sage: [h.get_coefficient(i) for i in range(10)] @@ -1628,11 +1622,11 @@ def is_nonzero(self): sage: from sage.data_structures.stream import (Stream_function, ....: Stream_cauchy_mul, Stream_cauchy_invert) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_cauchy_mul(f, f) + sage: g = Stream_cauchy_mul(f, f, True) sage: g.is_nonzero() False sage: fi = Stream_cauchy_invert(f) - sage: h = Stream_cauchy_mul(fi, fi) + sage: h = Stream_cauchy_mul(fi, fi, True) sage: h.is_nonzero() True """ @@ -1655,38 +1649,36 @@ class Stream_dirichlet_convolve(Stream_binary): sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_exact([0], True, constant=1) - sage: h = Stream_dirichlet_convolve(f, g) + sage: g = Stream_exact([0], constant=1) + sage: h = Stream_dirichlet_convolve(f, g, True) sage: [h[i] for i in range(1, 10)] [1, 3, 4, 7, 6, 12, 8, 15, 13] sage: [sigma(n) for n in range(1, 10)] [1, 3, 4, 7, 6, 12, 8, 15, 13] - sage: u = Stream_dirichlet_convolve(g, f) + sage: u = Stream_dirichlet_convolve(g, f, True) sage: [u[i] for i in range(1, 10)] [1, 3, 4, 7, 6, 12, 8, 15, 13] """ - def __init__(self, left, right): + def __init__(self, left, right, is_sparse): """ Initialize ``self``. sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_exact([1], True, constant=0) - sage: h = Stream_dirichlet_convolve(f, g) + sage: g = Stream_exact([1], constant=0) + sage: h = Stream_dirichlet_convolve(f, g, True) sage: h[1] Traceback (most recent call last): ... ValueError: Dirichlet convolution is only defined for coefficient streams with minimal index of nonzero coefficient at least 1 - sage: h = Stream_dirichlet_convolve(g, f) + sage: h = Stream_dirichlet_convolve(g, f, True) sage: h[1] Traceback (most recent call last): ... ValueError: Dirichlet convolution is only defined for coefficient streams with minimal index of nonzero coefficient at least 1 """ - if left._is_sparse != right._is_sparse: - raise NotImplementedError - super().__init__(left, right, left._is_sparse) + super().__init__(left, right, is_sparse) @lazy_attribute def _approximate_order(self): @@ -1696,9 +1688,9 @@ def _approximate_order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact, Stream_function, Stream_dirichlet_convolve - sage: f = Stream_exact([0, 2], True) + sage: f = Stream_exact([0, 2]) sage: g = Stream_function(lambda n: 3*n, True, 1) - sage: h = Stream_dirichlet_convolve(f, g) + sage: h = Stream_dirichlet_convolve(f, g, True) sage: h._approximate_order 1 sage: [h[i] for i in range(5)] @@ -1724,8 +1716,8 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_exact([0], True, constant=1) - sage: h = Stream_dirichlet_convolve(f, g) + sage: g = Stream_exact([0], constant=1) + sage: h = Stream_dirichlet_convolve(f, g, True) sage: h.get_coefficient(7) 8 sage: [h[i] for i in range(1, 10)] @@ -1766,7 +1758,7 @@ def __init__(self, series): TESTS:: sage: from sage.data_structures.stream import (Stream_exact, Stream_dirichlet_invert) - sage: f = Stream_exact([0, 0], True, constant=1) + sage: f = Stream_exact([0, 0], constant=1) sage: g = Stream_dirichlet_invert(f) sage: g[1] Traceback (most recent call last): @@ -1806,12 +1798,12 @@ def _ainv(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_exact, Stream_dirichlet_invert) - sage: f = Stream_exact([0, 3], True, constant=2) + sage: f = Stream_exact([0, 3], constant=2) sage: g = Stream_dirichlet_invert(f) sage: g._ainv 1/3 - sage: f = Stream_exact([Zmod(6)(5)], False, constant=2, order=1) + sage: f = Stream_exact([Zmod(6)(5)], constant=2, order=1) sage: g = Stream_dirichlet_invert(f) sage: g._ainv 5 @@ -1832,7 +1824,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_exact, Stream_dirichlet_invert) - sage: f = Stream_exact([0, 3], True, constant=2) + sage: f = Stream_exact([0, 3], constant=2) sage: g = Stream_dirichlet_invert(f) sage: g.get_coefficient(6) 2/27 @@ -1866,14 +1858,14 @@ class Stream_cauchy_compose(Stream_binary): sage: from sage.data_structures.stream import Stream_cauchy_compose, Stream_function sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_function(lambda n: 1, True, 1) - sage: h = Stream_cauchy_compose(f, g) + sage: h = Stream_cauchy_compose(f, g, True) sage: [h[i] for i in range(10)] [0, 1, 3, 8, 20, 48, 112, 256, 576, 1280] - sage: u = Stream_cauchy_compose(g, f) + sage: u = Stream_cauchy_compose(g, f, True) sage: [u[i] for i in range(10)] [0, 1, 3, 8, 21, 55, 144, 377, 987, 2584] """ - def __init__(self, f, g): + def __init__(self, f, g, is_sparse): """ Initialize ``self``. @@ -1882,11 +1874,11 @@ def __init__(self, f, g): sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose sage: f = Stream_function(lambda n: 1, True, 1) sage: g = Stream_function(lambda n: n^2, True, 1) - sage: h = Stream_cauchy_compose(f, g) + sage: h = Stream_cauchy_compose(f, g, True) """ if g._true_order and g._approximate_order <= 0: raise ValueError("can only compose with a series of positive valuation") - super().__init__(f, g, f._is_sparse) + super().__init__(f, g, is_sparse) @lazy_attribute def _approximate_order(self): @@ -1898,7 +1890,7 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_function(lambda n: n^2, True, 1) - sage: h = Stream_cauchy_compose(f, g) + sage: h = Stream_cauchy_compose(f, g, True) sage: h._approximate_order 1 sage: [h[i] for i in range(5)] @@ -1918,7 +1910,8 @@ def _approximate_order(self): # We need this for the case so self._neg_powers[0][n] => 0. self._neg_powers = [Stream_zero(self._left._is_sparse), ginv] for i in range(1, -self._left._approximate_order): - self._neg_powers.append(Stream_cauchy_mul(self._neg_powers[-1], ginv)) + # TODO: possibly we always want a dense cache here? + self._neg_powers.append(Stream_cauchy_mul(self._neg_powers[-1], ginv, self._is_sparse)) # placeholder None to make this 1-based. self._pos_powers = [None, self._right] @@ -1937,7 +1930,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_function(lambda n: n^2, True, 1) - sage: h = Stream_cauchy_compose(f, g) + sage: h = Stream_cauchy_compose(f, g, True) sage: h[5] # indirect doctest 527 sage: [h[i] for i in range(10)] # indirect doctest @@ -1950,7 +1943,8 @@ def get_coefficient(self, n): for i in range(fv, n // gv + 1)) # n > 0 while len(self._pos_powers) <= n // gv: - self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], self._right)) + # TODO: possibly we always want a dense cache here? + self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], self._right, self._is_sparse)) ret = sum(self._left[i] * self._neg_powers[-i][n] for i in range(fv, 0)) if n == 0: ret += self._left[0] @@ -1984,14 +1978,14 @@ class Stream_plethysm(Stream_binary): sage: p = SymmetricFunctions(QQ).p() sage: f = Stream_function(lambda n: s[n], True, 1) sage: g = Stream_function(lambda n: s[[1]*n], True, 1) - sage: h = Stream_plethysm(f, g, p, s) + sage: h = Stream_plethysm(f, g, True, p, s) sage: [h[i] for i in range(5)] [0, s[1], s[1, 1] + s[2], 2*s[1, 1, 1] + s[2, 1] + s[3], 3*s[1, 1, 1, 1] + 2*s[2, 1, 1] + s[2, 2] + s[3, 1] + s[4]] - sage: u = Stream_plethysm(g, f, p, s) + sage: u = Stream_plethysm(g, f, True, p, s) sage: [u[i] for i in range(5)] [0, s[1], @@ -2003,9 +1997,9 @@ class Stream_plethysm(Stream_binary): stream of order `0`:: sage: from sage.data_structures.stream import Stream_exact - sage: f = Stream_exact([s[1]], True, order=1) + sage: f = Stream_exact([s[1]], order=1) sage: g = Stream_function(lambda n: s[n], True, 0) - sage: r = Stream_plethysm(f, g, p, s) + sage: r = Stream_plethysm(f, g, True, p, s) sage: [r[n] for n in range(3)] [s[], s[1], s[2]] @@ -2013,15 +2007,15 @@ class Stream_plethysm(Stream_binary): Check corner cases:: - sage: f0 = Stream_exact([p([])], True) - sage: f1 = Stream_exact([p[1]], True, order=1) - sage: f2 = Stream_exact([p[2]], True, order=2 ) - sage: f11 = Stream_exact([p[1,1]], True, order=2 ) - sage: r = Stream_plethysm(f0, f1, p); [r[n] for n in range(3)] + sage: f0 = Stream_exact([p([])]) + sage: f1 = Stream_exact([p[1]], order=1) + sage: f2 = Stream_exact([p[2]], order=2 ) + sage: f11 = Stream_exact([p[1,1]], order=2 ) + sage: r = Stream_plethysm(f0, f1, True, p); [r[n] for n in range(3)] [p[], 0, 0] - sage: r = Stream_plethysm(f0, f2, p); [r[n] for n in range(3)] + sage: r = Stream_plethysm(f0, f2, True, p); [r[n] for n in range(3)] [p[], 0, 0] - sage: r = Stream_plethysm(f0, f11, p); [r[n] for n in range(3)] + sage: r = Stream_plethysm(f0, f11, True, p); [r[n] for n in range(3)] [p[], 0, 0] Check that degree one elements are treated in the correct way:: @@ -2030,21 +2024,21 @@ class Stream_plethysm(Stream_binary): sage: f_s = a1*p[1] + a2*p[2] + a11*p[1,1] sage: g_s = b1*p[1] + b21*p[2,1] + b111*p[1,1,1] sage: r_s = f_s(g_s) - sage: f = Stream_exact([f_s.restrict_degree(k) for k in range(f_s.degree()+1)], True) - sage: g = Stream_exact([g_s.restrict_degree(k) for k in range(g_s.degree()+1)], True) - sage: r = Stream_plethysm(f, g, p) + sage: f = Stream_exact([f_s.restrict_degree(k) for k in range(f_s.degree()+1)]) + sage: g = Stream_exact([g_s.restrict_degree(k) for k in range(g_s.degree()+1)]) + sage: r = Stream_plethysm(f, g, True, p) sage: r_s == sum(r[n] for n in range(2*(r_s.degree()+1))) True sage: r_s - f_s(g_s, include=[]) (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] - sage: r2 = Stream_plethysm(f, g, p, include=[]) + sage: r2 = Stream_plethysm(f, g, True, p, include=[]) sage: r_s - sum(r2[n] for n in range(2*(r_s.degree()+1))) (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] """ - def __init__(self, f, g, p, ring=None, include=None, exclude=None): + def __init__(self, f, g, is_sparse, p, ring=None, include=None, exclude=None): r""" Initialize ``self``. @@ -2055,7 +2049,7 @@ def __init__(self, f, g, p, ring=None, include=None, exclude=None): sage: p = SymmetricFunctions(QQ).p() sage: f = Stream_function(lambda n: s[n], True, 1) sage: g = Stream_function(lambda n: s[n-1,1], True, 2) - sage: h = Stream_plethysm(f, g, p) + sage: h = Stream_plethysm(f, g, True, p) """ if isinstance(f, Stream_exact): self._degree_f = f._degree @@ -2094,7 +2088,7 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_plethysm sage: p = SymmetricFunctions(QQ).p() sage: f = Stream_function(lambda n: p[n], True, 1) - sage: h = Stream_plethysm(f, f, p) + sage: h = Stream_plethysm(f, f, True, p) sage: h._approximate_order 1 sage: [h[i] for i in range(5)] @@ -2123,7 +2117,7 @@ def get_coefficient(self, n): sage: p = SymmetricFunctions(QQ).p() sage: f = Stream_function(lambda n: s[n], True, 1) sage: g = Stream_function(lambda n: s[[1]*n], True, 1) - sage: h = Stream_plethysm(f, g, p) + sage: h = Stream_plethysm(f, g, True, p) sage: s(h.get_coefficient(5)) 4*s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 2*s[2, 2, 1] + 2*s[3, 1, 1] + s[3, 2] + s[4, 1] + s[5] sage: [s(h.get_coefficient(i)) for i in range(6)] @@ -2160,9 +2154,9 @@ def compute_product(self, n, la): sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function, Stream_zero sage: s = SymmetricFunctions(QQ).s() sage: p = SymmetricFunctions(QQ).p() - sage: f = Stream_exact([1], False) # irrelevant for this test - sage: g = Stream_exact([s[2], s[3]], False, 0, 4, 2) - sage: h = Stream_plethysm(f, g, p) + sage: f = Stream_exact([1]) # irrelevant for this test + sage: g = Stream_exact([s[2], s[3]], 0, 4, 2) + sage: h = Stream_plethysm(f, g, True, p) sage: A = h.compute_product(7, Partition([2, 1])); A 1/12*p[2, 2, 1, 1, 1] + 1/4*p[2, 2, 2, 1] + 1/6*p[3, 2, 2] + 1/12*p[4, 1, 1, 1] + 1/4*p[4, 2, 1] + 1/6*p[4, 3] @@ -2170,18 +2164,18 @@ def compute_product(self, n, la): True sage: p2 = tensor([p, p]) - sage: f = Stream_exact([1], True) # irrelevant for this test + sage: f = Stream_exact([1]) # irrelevant for this test sage: g = Stream_function(lambda n: sum(tensor([p[k], p[n-k]]) for k in range(n+1)), True, 1) - sage: h = Stream_plethysm(f, g, p2) + sage: h = Stream_plethysm(f, g, True, p2) sage: A = h.compute_product(7, Partition([2, 1])) sage: B = p[2, 1](sum(g[n] for n in range(7))) sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 7}) sage: A == B True - sage: f = Stream_exact([1], True) # irrelevant for this test + sage: f = Stream_exact([1]) # irrelevant for this test sage: g = Stream_function(lambda n: s[n], True, 0) - sage: h = Stream_plethysm(f, g, p) + sage: h = Stream_plethysm(f, g, True, p) sage: B = p[2, 2, 1](sum(s[i] for i in range(7))) sage: all(h.compute_product(k, Partition([2, 2, 1])) == B.restrict_degree(k) for k in range(7)) True @@ -2220,17 +2214,17 @@ def stretched_power_restrict_degree(self, i, m, d): sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function, Stream_zero sage: s = SymmetricFunctions(QQ).s() sage: p = SymmetricFunctions(QQ).p() - sage: f = Stream_exact([1], False) # irrelevant for this test - sage: g = Stream_exact([s[2], s[3]], False, 0, 4, 2) - sage: h = Stream_plethysm(f, g, p) + sage: f = Stream_exact([1]) # irrelevant for this test + sage: g = Stream_exact([s[2], s[3]], 0, 4, 2) + sage: h = Stream_plethysm(f, g, True, p) sage: A = h.stretched_power_restrict_degree(2, 3, 6) sage: A == p[2,2,2](s[2] + s[3]).homogeneous_component(12) True sage: p2 = tensor([p, p]) - sage: f = Stream_exact([1], True) # irrelevant for this test + sage: f = Stream_exact([1]) # irrelevant for this test sage: g = Stream_function(lambda n: sum(tensor([p[k], p[n-k]]) for k in range(n+1)), True, 1) - sage: h = Stream_plethysm(f, g, p2) + sage: h = Stream_plethysm(f, g, True, p2) sage: A = h.stretched_power_restrict_degree(2, 3, 6) sage: B = p[2,2,2](sum(g[n] for n in range(7))) # long time sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 12}) # long time @@ -2238,7 +2232,8 @@ def stretched_power_restrict_degree(self, i, m, d): True """ while len(self._powers) < m: - self._powers.append(Stream_cauchy_mul(self._powers[-1], self._powers[0])) + # TODO: possibly we always want a dense cache here? + self._powers.append(Stream_cauchy_mul(self._powers[-1], self._powers[0], self._is_sparse)) power_d = self._powers[m-1][d] # we have to check power_d for zero because it might be an # integer and not a symmetric function @@ -2566,7 +2561,7 @@ def __init__(self, series, approximate_order=None): TESTS:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_exact) - sage: f = Stream_exact([1, -1], False) + sage: f = Stream_exact([1, -1]) sage: g = Stream_cauchy_invert(f) """ super().__init__(series, series._is_sparse) @@ -2604,12 +2599,12 @@ def _ainv(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_exact) - sage: f = Stream_exact([2, -3], False) + sage: f = Stream_exact([2, -3]) sage: g = Stream_cauchy_invert(f) sage: g._ainv 1/2 - sage: f = Stream_exact([Zmod(6)(5)], False, constant=2) + sage: f = Stream_exact([Zmod(6)(5)], constant=2) sage: g = Stream_cauchy_invert(f) sage: g._ainv 5 @@ -2844,7 +2839,7 @@ def __init__(self, series, shift): sage: from sage.data_structures.stream import Stream_shift sage: from sage.data_structures.stream import Stream_exact - sage: h = Stream_exact([1], False, constant=3) + sage: h = Stream_exact([1], constant=3) sage: M = Stream_shift(h, 2) sage: TestSuite(M).run(skip="_test_pickling") """ @@ -2959,7 +2954,7 @@ def __init__(self, series, shift): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact, Stream_derivative - sage: f = Stream_exact([1,2,3], False) + sage: f = Stream_exact([1,2,3]) sage: f2 = Stream_derivative(f, 2) sage: TestSuite(f2).run() """ @@ -3062,7 +3057,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_exact, Stream_derivative - sage: f = Stream_exact([1,2], False) + sage: f = Stream_exact([1,2]) sage: Stream_derivative(f, 1).is_nonzero() True sage: Stream_derivative(f, 2).is_nonzero() # it might be nice if this gave False diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 48af601a570..a4d8005167f 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -574,7 +574,6 @@ def map_coefficients(self, f): if not any(initial_coefficients) and not c: return P.zero() coeff_stream = Stream_exact(initial_coefficients, - self._coeff_stream._is_sparse, order=coeff_stream._approximate_order, degree=coeff_stream._degree, constant=P.base_ring()(c)) @@ -626,8 +625,7 @@ def truncate(self, d): coeff_stream = self._coeff_stream v = coeff_stream._approximate_order initial_coefficients = [coeff_stream[i] for i in range(v, d)] - return P.element_class(P, Stream_exact(initial_coefficients, P._sparse, - order=v)) + return P.element_class(P, Stream_exact(initial_coefficients, order=v)) def shift(self, n): r""" @@ -687,7 +685,7 @@ def shift(self, n): init_coeff = self._coeff_stream._initial_coefficients degree = self._coeff_stream._degree + n valuation = self._coeff_stream._approximate_order + n - coeff_stream = Stream_exact(init_coeff, self._coeff_stream._is_sparse, + coeff_stream = Stream_exact(init_coeff, constant=self._coeff_stream._constant, order=valuation, degree=degree) else: @@ -945,7 +943,7 @@ def __bool__(self): return False if isinstance(self._coeff_stream._target, Stream_exact): return True - if self.parent()._sparse: + if self._coeff_stream._is_sparse: cache = self._coeff_stream._cache if any(cache[a] for a in cache): return True @@ -1506,13 +1504,13 @@ def _add_(self, other): if not any(initial_coefficients) and not constant: return P.zero() coeff_stream = Stream_exact(initial_coefficients, - P._sparse, constant=constant, degree=degree, order=approximate_order) return P.element_class(P, coeff_stream) return P.element_class(P, Stream_add(self._coeff_stream, - other._coeff_stream)) + other._coeff_stream, + P.is_sparse())) def _sub_(self, other): """ @@ -1584,14 +1582,15 @@ def _sub_(self, other): if not any(initial_coefficients) and not constant: return P.zero() coeff_stream = Stream_exact(initial_coefficients, - P._sparse, constant=constant, degree=degree, order=approximate_order) return P.element_class(P, coeff_stream) if left == right: return P.zero() - return P.element_class(P, Stream_sub(self._coeff_stream, other._coeff_stream)) + return P.element_class(P, Stream_sub(self._coeff_stream, + other._coeff_stream, + P.is_sparse())) def _acted_upon_(self, scalar, self_on_left): r""" @@ -1729,7 +1728,6 @@ def _acted_upon_(self, scalar, self_on_left): c = scalar * coeff_stream._constant initial_coefficients = [scalar * val for val in init_coeffs] return P.element_class(P, Stream_exact(initial_coefficients, - P._sparse, order=v, constant=c, degree=coeff_stream._degree)) @@ -1791,7 +1789,6 @@ def _neg_(self): initial_coefficients = [-v for v in coeff_stream._initial_coefficients] constant = -coeff_stream._constant coeff_stream = Stream_exact(initial_coefficients, - P._sparse, constant=constant, degree=coeff_stream._degree, order=coeff_stream.order()) @@ -2713,12 +2710,11 @@ def _mul_(self, other): else: c = left._constant # this is zero coeff_stream = Stream_exact(initial_coefficients, - P._sparse, order=lv + rv, constant=c) return P.element_class(P, coeff_stream) - return P.element_class(P, Stream_cauchy_mul(left, right)) + return P.element_class(P, Stream_cauchy_mul(left, right, P.is_sparse())) def __pow__(self, n): r""" @@ -2794,7 +2790,6 @@ def __pow__(self, n): deg = ret.degree() + 1 initial_coefficients = [ret[i] for i in range(val, deg)] return P.element_class(P, Stream_exact(initial_coefficients, - P._sparse, constant=cs._constant, degree=deg, order=val)) @@ -2876,7 +2871,6 @@ def __invert__(self): v = -coeff_stream.order() c = P._internal_poly_ring.base_ring().zero() coeff_stream = Stream_exact((i, -i), - P._sparse, order=v, constant=c) return P.element_class(P, coeff_stream) @@ -2885,7 +2879,6 @@ def __invert__(self): v = -coeff_stream.order() c = P._internal_poly_ring.base_ring().zero() coeff_stream = Stream_exact((i,), - P._sparse, order=v, constant=c) return P.element_class(P, coeff_stream) @@ -2895,7 +2888,6 @@ def __invert__(self): v = -coeff_stream.order() c = ~initial_coefficients[0] coeff_stream = Stream_exact((), - P._sparse, order=v, constant=c) return P.element_class(P, coeff_stream) @@ -3055,7 +3047,6 @@ def _div_(self, other): initial_coefficients = [num[i] / d for i in range(v, num.degree() + 1)] order = v - den.valuation() return P.element_class(P, Stream_exact(initial_coefficients, - P._sparse, order=order, constant=0)) @@ -3081,12 +3072,10 @@ def _div_(self, other): order = 0 initial_coefficients = [quo[i] for i in range(order, quo.degree() + 1)] return P.element_class(P, Stream_exact(initial_coefficients, - P._sparse, order=order, degree=v, constant=constant)) return P.element_class(P, Stream_exact([], - P._sparse, order=v, degree=v, constant=constant)) @@ -3095,7 +3084,7 @@ def _div_(self, other): # P._minimal_valuation is zero, because we allow division by # series of positive valuation right_inverse = Stream_cauchy_invert(right) - return P.element_class(P, Stream_cauchy_mul(left, right_inverse)) + return P.element_class(P, Stream_cauchy_mul(left, right_inverse, P.is_sparse())) def _floordiv_(self, other): @@ -3583,7 +3572,6 @@ def __call__(self, g, *, check=True): deg = ret.degree() + 1 initial_coefficients = [ret[i] for i in range(val, deg)] coeff_stream = Stream_exact(initial_coefficients, - self._coeff_stream._is_sparse, constant=P.base_ring().zero(), degree=deg, order=val) return P.element_class(P, coeff_stream) @@ -3652,7 +3640,9 @@ def coefficient(n): coeff_stream = Stream_function(coefficient, P._sparse, 1) return P.element_class(P, coeff_stream) - coeff_stream = Stream_cauchy_compose(self._coeff_stream, g._coeff_stream) + coeff_stream = Stream_cauchy_compose(self._coeff_stream, + g._coeff_stream, + P.is_sparse()) return P.element_class(P, coeff_stream) compose = __call__ @@ -3807,7 +3797,6 @@ def revert(self): a = coeff_stream[0] b = coeff_stream[1] coeff_stream = Stream_exact((-a/b, 1/b), - coeff_stream._is_sparse, order=0) return P.element_class(P, coeff_stream) @@ -3910,7 +3899,6 @@ def derivative(self, *args): for i, c in enumerate(coeff_stream._initial_coefficients, coeff_stream._approximate_order)] coeff_stream = Stream_exact(coeffs, - self._coeff_stream._is_sparse, order=coeff_stream._approximate_order - order, constant=coeff_stream._constant) return P.element_class(P, coeff_stream) @@ -4462,7 +4450,9 @@ def coefficient(n): coeff_stream = Stream_function(coefficient, P._sparse, 1) return P.element_class(P, coeff_stream) - coeff_stream = Stream_cauchy_compose(self._coeff_stream, g0._coeff_stream) + coeff_stream = Stream_cauchy_compose(self._coeff_stream, + g0._coeff_stream, + P.is_sparse()) return P.element_class(P, coeff_stream) # The arity is at least 2 @@ -4606,7 +4596,6 @@ def revert(self): a = coeff_stream[0] b = coeff_stream[1] coeff_stream = Stream_exact((-a/b, 1/b), - coeff_stream._is_sparse, order=0) return P.element_class(P, coeff_stream) @@ -4707,7 +4696,6 @@ def derivative(self, *args): for i, c in enumerate(coeff_stream._initial_coefficients, coeff_stream._approximate_order)] coeff_stream = Stream_exact(coeffs, - self._coeff_stream._is_sparse, order=coeff_stream._approximate_order - order, constant=coeff_stream._constant) return P.element_class(P, coeff_stream) @@ -5337,7 +5325,7 @@ def __call__(self, *args, check=True): else: ps = tensor([R._sets[0].realization_of().p()]*P._arity) coeff_stream = Stream_plethysm(self._coeff_stream, g._coeff_stream, - ps, R) + P.is_sparse(), ps, R) return P.element_class(P, coeff_stream) else: @@ -5438,7 +5426,6 @@ def revert(self): b = coeff_stream[1][Partition([1])] X = R(Partition([1])) coeff_stream = Stream_exact((-a/b, 1/b * X), - coeff_stream._is_sparse, order=0) return P.element_class(P, coeff_stream) @@ -6132,7 +6119,7 @@ def _mul_(self, other): and right._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and right.order() == 1): return self # other == 1 - coeff = Stream_dirichlet_convolve(left, right) + coeff = Stream_dirichlet_convolve(left, right, P.is_sparse()) # Performing exact arithmetic is slow because the series grow large # very quickly as we are multiplying the degree #if (isinstance(left, Stream_exact) and not left._constant @@ -6142,7 +6129,7 @@ def _mul_(self, other): # deg = (left._degree - 1) * (right._degree - 1) + 1 # order = left._approximate_order * right._approximate_order # coeff_vals = [coeff[i] for i in range(order, deg)] - # return P.element_class(P, Stream_exact(coeff_vals, coeff._is_sparse, + # return P.element_class(P, Stream_exact(coeff_vals, # constant=left._constant, order=order, degree=deg)) return P.element_class(P, coeff) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index c0e0d536562..fb7b3177e17 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -473,18 +473,18 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No raise ValueError("you must specify the degree for the polynomial 0") degree = valuation if x == R.zero(): - coeff_stream = Stream_exact([], self._sparse, order=degree, constant=constant) + coeff_stream = Stream_exact([], order=degree, constant=constant) return self.element_class(self, coeff_stream) initial_coefficients = [x[i] for i in range(x.valuation(), x.degree() + 1)] - coeff_stream = Stream_exact(initial_coefficients, self._sparse, + coeff_stream = Stream_exact(initial_coefficients, order=x.valuation(), degree=degree, constant=constant) return self.element_class(self, coeff_stream) # Handle when it is a lazy series if isinstance(x, self.Element): - if x._coeff_stream._is_sparse is not self._sparse: + # if x._coeff_stream._is_sparse is not self._sparse: # TODO: Implement a way to make a self._sparse copy - raise NotImplementedError("cannot convert between sparse and dense") + # raise NotImplementedError("cannot convert between sparse and dense") # If x is known to be 0 if isinstance(x._coeff_stream, Stream_zero): @@ -496,8 +496,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No if valuation is None: raise ValueError("you must specify the degree for the polynomial 0") degree = valuation - coeff_stream = Stream_exact([], self._sparse, order=degree, - constant=constant) + coeff_stream = Stream_exact([], order=degree, constant=constant) return self.element_class(self, coeff_stream) # Make the result exact @@ -513,7 +512,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No # We learned some stuff about x; pass it along x._coeff_stream._approximate_order += len(initial_coefficients) initial_coefficients = [] - coeff_stream = Stream_exact(initial_coefficients, self._sparse, + coeff_stream = Stream_exact(initial_coefficients, order=valuation, degree=degree, constant=constant) return self.element_class(self, coeff_stream) @@ -577,8 +576,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No p = [BR(c) for c, _ in zip(_skip_leading_zeros(x), range(valuation, degree))] if not any(p) and not constant: return self.zero() - coeff_stream = Stream_exact(p, self._sparse, order=valuation, - constant=constant, degree=degree) + coeff_stream = Stream_exact(p, order=valuation, constant=constant, degree=degree) return self.element_class(self, coeff_stream) raise ValueError(f"unable to convert {x} into {self}") @@ -700,7 +698,7 @@ def one(self): """ R = self.base_ring() - coeff_stream = Stream_exact([R.one()], self._sparse, constant=R.zero(), order=0) + coeff_stream = Stream_exact([R.one()], constant=R.zero(), order=0) return self.element_class(self, coeff_stream) @cached_method @@ -1269,8 +1267,7 @@ def gen(self, n=0): if n != 0: raise IndexError("there is only one generator") R = self.base_ring() - coeff_stream = Stream_exact([R.one()], self._sparse, - constant=R.zero(), order=1) + coeff_stream = Stream_exact([R.one()], constant=R.zero(), order=1) return self.element_class(self, coeff_stream) def ngens(self): @@ -1430,7 +1427,7 @@ def series(self, coefficient, valuation, degree=None, constant=None): constant = self.base_ring().zero() if degree is None: degree = valuation + len(coefficient) - coeff_stream = Stream_exact(coefficient, self._sparse, order=valuation, + coeff_stream = Stream_exact(coefficient, order=valuation, constant=constant, degree=degree) return self.element_class(self, coeff_stream) @@ -1678,9 +1675,9 @@ def gen(self, n=0): R = self._laurent_poly_ring BR = self.base_ring() if len(self.variable_names()) == 1: - coeff_stream = Stream_exact([BR.one()], self._sparse, constant=BR.zero(), order=1) + coeff_stream = Stream_exact([BR.one()], constant=BR.zero(), order=1) else: - coeff_stream = Stream_exact([R.gen(n)], self._sparse, constant=BR.zero(), order=1) + coeff_stream = Stream_exact([R.gen(n)], constant=BR.zero(), order=1) return self.element_class(self, coeff_stream) def ngens(self): @@ -1853,7 +1850,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No coeff_stream = Stream_zero(self._sparse) else: if not x: - coeff_stream = Stream_exact([], self._sparse, + coeff_stream = Stream_exact([], order=valuation, degree=degree, constant=constant) @@ -1871,14 +1868,14 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No d = max(p_dict.keys()) p_list = [p_dict.get(i, 0) for i in range(v, d + 1)] - coeff_stream = Stream_exact(p_list, self._sparse, + coeff_stream = Stream_exact(p_list, order=v, constant=constant, degree=degree) return self.element_class(self, coeff_stream) if isinstance(x, LazyPowerSeries): - if x._coeff_stream._is_sparse is self._sparse: + # if x._coeff_stream._is_sparse is self._sparse: stream = x._coeff_stream if isinstance(stream, Stream_exact): if self._arity == 1: @@ -1901,7 +1898,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No valuation=valuation) return self.element_class(self, stream) # TODO: Implement a way to make a self._sparse copy - raise NotImplementedError("cannot convert between sparse and dense") + # raise NotImplementedError("cannot convert between sparse and dense") if callable(x) or isinstance(x, (GeneratorType, map, filter)): if valuation is None: @@ -1920,7 +1917,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if not all(e.is_homogeneous() and e.degree() == i for i, e in enumerate(p, valuation)): raise ValueError("coefficients must be homogeneous polynomials of the correct degree") - coeff_stream = Stream_exact(p, self._sparse, + coeff_stream = Stream_exact(p, order=valuation, constant=constant, degree=degree) @@ -2336,17 +2333,17 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No d = max(p_dict) p_list = [p_dict.get(i, 0) for i in range(v, d + 1)] - coeff_stream = Stream_exact(p_list, self._sparse, + coeff_stream = Stream_exact(p_list, order=v, constant=0, degree=degree) return self.element_class(self, coeff_stream) if isinstance(x, self.Element): - if x._coeff_stream._is_sparse is self._sparse: + # if x._coeff_stream._is_sparse is self._sparse: return self.element_class(self, x._coeff_stream) # TODO: Implement a way to make a self._sparse copy - raise NotImplementedError("cannot convert between sparse and dense") + # raise NotImplementedError("cannot convert between sparse and dense") if self._arity == 1: def check_homogeneous_of_degree(f, d): @@ -2383,7 +2380,7 @@ def check_homogeneous_of_degree(f, d): p = [R(e) for e in x] for i, e in enumerate(p, valuation): check_homogeneous_of_degree(e, i) - coeff_stream = Stream_exact(p, self._sparse, + coeff_stream = Stream_exact(p, order=valuation, constant=0, degree=degree) @@ -2393,7 +2390,7 @@ def check_homogeneous_of_degree(f, d): p = [R(x(i)) for i in range(valuation, degree)] for i, e in enumerate(p, valuation): check_homogeneous_of_degree(e, i) - coeff_stream = Stream_exact(p, self._sparse, + coeff_stream = Stream_exact(p, order=valuation, constant=0, degree=degree) @@ -2622,7 +2619,7 @@ def one(self): 1 + O(1/(8^z)) """ R = self.base_ring() - coeff_stream = Stream_exact([R.one()], self._sparse, constant=R.zero(), order=1) + coeff_stream = Stream_exact([R.one()], constant=R.zero(), order=1) return self.element_class(self, coeff_stream) def _coerce_map_from_(self, S): @@ -2766,7 +2763,7 @@ def _an_element_(self): 1/(4^z) + 1/(5^z) + 1/(6^z) + O(1/(7^z)) """ c = self.base_ring().an_element() - return self.element_class(self, Stream_exact([], self._sparse, constant=c, order=4)) + return self.element_class(self, Stream_exact([], constant=c, order=4)) def some_elements(self): """ From d823d95aba118930147401ec20b120b2dff8ae4d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 7 Oct 2022 20:30:52 -0700 Subject: [PATCH 164/414] build/pkgs/meson_python: Update to 0.10.0 --- build/pkgs/meson_python/checksums.ini | 6 +++--- build/pkgs/meson_python/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/meson_python/checksums.ini b/build/pkgs/meson_python/checksums.ini index cc19a05eee3..6c2854e30a4 100644 --- a/build/pkgs/meson_python/checksums.ini +++ b/build/pkgs/meson_python/checksums.ini @@ -1,5 +1,5 @@ tarball=meson_python-VERSION.tar.gz -sha1=87b086c28d626182056d020d8e4eeafa8bb8ccee -md5=4774f7348d5fc6769e62716600d3773a -cksum=3965133995 +sha1=6ff574d17feb98165a2678c78a429d3ff32aaa7b +md5=afd824bf61e79cd5eeab2a6dc7863eaf +cksum=3939516041 upstream_url=https://pypi.io/packages/source/m/meson_python/meson_python-VERSION.tar.gz diff --git a/build/pkgs/meson_python/package-version.txt b/build/pkgs/meson_python/package-version.txt index ac39a106c48..78bc1abd14f 100644 --- a/build/pkgs/meson_python/package-version.txt +++ b/build/pkgs/meson_python/package-version.txt @@ -1 +1 @@ -0.9.0 +0.10.0 From 790a684e10ee9b0d632fb8448ac77d44dc5a42c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 9 Oct 2022 13:22:56 +0200 Subject: [PATCH 165/414] refresh the file categories/rings.py --- src/sage/categories/rings.py | 197 ++++++++++++++++------------------- 1 file changed, 88 insertions(+), 109 deletions(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index 88ce6ef5bc0..d43dc15bbb1 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -1,22 +1,22 @@ r""" Rings """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 David Kohel # William Stein # 2008 Teresa Gomez-Diaz (CNRS) # 2008-2011 Nicolas M. Thiery # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** +# https://www.gnu.org/licenses/ +# ***************************************************************************** +from functools import reduce from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import LazyImport from sage.categories.category_with_axiom import CategoryWithAxiom from sage.categories.rngs import Rngs from sage.structure.element import Element -from functools import reduce class Rings(CategoryWithAxiom): @@ -49,19 +49,18 @@ class Rings(CategoryWithAxiom): .. TODO:: - (see: http://trac.sagemath.org/sage_trac/wiki/CategoriesRoadMap) + (see: https://trac.sagemath.org/sage_trac/wiki/CategoriesRoadMap) - Make Rings() into a subcategory or alias of Algebras(ZZ); - A parent P in the category ``Rings()`` should automatically be in the category ``Algebras(P)``. """ - _base_category_class_and_axiom = (Rngs, "Unital") class MorphismMethods: @cached_method - def is_injective(self): + def is_injective(self) -> bool: """ Return whether or not this morphism is injective. @@ -186,7 +185,7 @@ def is_injective(self): raise NotImplementedError - def _is_nonzero(self): + def _is_nonzero(self) -> bool: r""" Return whether this is not the zero morphism. @@ -255,7 +254,6 @@ def extend_to_fraction_field(self): parent = domain.Hom(codomain) # category = category=self.category_for() ??? return RingHomomorphism_from_fraction_field(parent, self) - class SubcategoryMethods: def NoZeroDivisors(self): @@ -273,11 +271,6 @@ def NoZeroDivisors(self): sage: Rings().NoZeroDivisors() Category of domains - .. NOTE:: - - This could be generalized to - :class:`MagmasAndAdditiveMagmas.Distributive.AdditiveUnital`. - TESTS:: sage: TestSuite(Rings().NoZeroDivisors()).run() @@ -293,11 +286,6 @@ def Division(self): A ring satisfies the *division axiom* if all non-zero elements have multiplicative inverses. - .. NOTE:: - - This could be generalized to - :class:`MagmasAndAdditiveMagmas.Distributive.AdditiveUnital`. - EXAMPLES:: sage: Rings().Division() @@ -313,15 +301,14 @@ def Division(self): """ return self._with_axiom('Division') - NoZeroDivisors = LazyImport('sage.categories.domains', 'Domains', at_startup=True) - Division = LazyImport('sage.categories.division_rings', 'DivisionRings', at_startup=True) - Commutative = LazyImport('sage.categories.commutative_rings', 'CommutativeRings', at_startup=True) + Division = LazyImport('sage.categories.division_rings', 'DivisionRings', at_startup=True) + Commutative = LazyImport('sage.categories.commutative_rings', 'CommutativeRings', at_startup=True) class ParentMethods: - def is_ring(self): + def is_ring(self) -> bool: """ - Return True, since this in an object of the category of rings. + Return ``True``, since this in an object of the category of rings. EXAMPLES:: @@ -330,7 +317,7 @@ def is_ring(self): """ return True - def is_zero(self): + def is_zero(self) -> bool: """ Return ``True`` if this is the zero ring. @@ -355,11 +342,11 @@ def is_zero(self): def bracket(self, x, y): """ - Returns the Lie bracket `[x, y] = x y - y x` of `x` and `y`. + Return the Lie bracket `[x, y] = x y - y x` of `x` and `y`. INPUT: - - ``x``, ``y`` -- elements of ``self`` + - ``x``, ``y`` -- elements of ``self`` EXAMPLES:: @@ -377,11 +364,11 @@ def bracket(self, x, y): sage: F.bracket( F.bracket(a,b), c) + F.bracket(F.bracket(b,c),a) + F.bracket(F.bracket(c,a),b) 0 """ - return x*y - y*x + return x * y - y * x def _Hom_(self, Y, category): r""" - Returns the homset from ``self`` to ``Y`` in the category ``category`` + Return the homset from ``self`` to ``Y`` in the category ``category``. INPUT: @@ -415,9 +402,9 @@ def _Hom_(self, Y, category): sage: TestSuite(Hom(QQ, QQ, category = Rings())).run() # indirect doctest """ if category is not None and not category.is_subcategory(Rings()): - raise TypeError("%s is not a subcategory of Rings()"%category) + raise TypeError(f"{category} is not a subcategory of Rings()") if Y not in Rings(): - raise TypeError("%s is not a ring" % Y) + raise TypeError(f"{Y} is not a ring") from sage.rings.homset import RingHomset return RingHomset(self, Y, category=category) @@ -428,16 +415,15 @@ def _mul_(self, x, switch_sides=False): """ Multiplication of rings with, e.g., lists. - NOTE: + .. NOTE:: - This method is used to create ideals. It is - the same as the multiplication method for - :class:`~sage.rings.ring.Ring`. However, not - all parents that belong to the category of - rings also inherits from the base class of - rings. Therefore, we implemented a ``__mul__`` - method for parents, that calls a ``_mul_`` - method implemented here. See :trac:`7797`. + This method is used to create ideals. It is the same + as the multiplication method for + :class:`~sage.rings.ring.Ring`. However, not all + parents that belong to the category of rings also + inherits from the base class of rings. Therefore, we + implemented a ``__mul__`` method for parents, that + calls a ``_mul_`` method implemented here. See :trac:`7797`. INPUT: @@ -480,12 +466,11 @@ def _mul_(self, x, switch_sides=False): AUTHOR: - Simon King (2011-03-22) - """ try: if self.is_commutative(): return self.ideal(x) - except (AttributeError,NotImplementedError): + except (AttributeError, NotImplementedError): pass try: side = x.side() @@ -495,19 +480,19 @@ def _mul_(self, x, switch_sides=False): try: x = x.gens() except (AttributeError, NotImplementedError): - pass # ... not an ideal + pass # ... not an ideal if switch_sides: - if side in ['right','twosided']: - return self.ideal(x,side=side) - elif side=='left': - return self.ideal(x,side='twosided') + if side in ['right', 'twosided']: + return self.ideal(x, side=side) + elif side == 'left': + return self.ideal(x, side='twosided') else: - if side in ['left','twosided']: - return self.ideal(x,side=side) - elif side=='right': - return self.ideal(x,side='twosided') + if side in ['left', 'twosided']: + return self.ideal(x, side=side) + elif side == 'right': + return self.ideal(x, side='twosided') # duck typing failed - raise TypeError("Don't know how to transform %s into an ideal of %s"%(x,self)) + raise TypeError("do not know how to transform %s into an ideal of %s" % (x, self)) def __pow__(self, n): """ @@ -537,12 +522,12 @@ def ideal_monoid(self): """ The monoid of the ideals of this ring. - NOTE: + .. NOTE:: - The code is copied from the base class of rings. - This is since there are rings that do not inherit - from that class, such as matrix algebras. See - :trac:`7797`. + The code is copied from the base class of rings. + This is since there are rings that do not inherit + from that class, such as matrix algebras. See + :trac:`7797`. EXAMPLES:: @@ -559,7 +544,6 @@ def ideal_monoid(self): sage: MS.ideal_monoid() is MS.ideal_monoid() True - """ try: from sage.rings.ideal_monoid import IdealMonoid @@ -602,7 +586,8 @@ def _test_characteristic(self, **options): try: characteristic = self.characteristic() except AttributeError: - return # raised when self.one() does not have a additive_order() + # raised when self.one() does not have a additive_order() + return except NotImplementedError: return @@ -614,13 +599,13 @@ def ideal(self, *args, **kwds): """ Create an ideal of this ring. - NOTE: + .. NOTE:: - The code is copied from the base class - :class:`~sage.rings.ring.Ring`. This is - because there are rings that do not inherit - from that class, such as matrix algebras. - See :trac:`7797`. + The code is copied from the base class + :class:`~sage.rings.ring.Ring`. This is + because there are rings that do not inherit + from that class, such as matrix algebras. + See :trac:`7797`. INPUT: @@ -656,7 +641,6 @@ def ideal(self, *args, **kwds): [0 0] ) of Full MatrixSpace of 2 by 2 dense matrices over Rational Field - """ if 'coerce' in kwds: coerce = kwds['coerce'] @@ -690,12 +674,12 @@ def ideal(self, *args, **kwds): else: try: if self.has_coerce_map_from(first): - gens = first.gens() # we have a ring as argument + gens = first.gens() # we have a ring as argument elif isinstance(first, Element): gens = [first] else: - raise ArithmeticError("There is no coercion from %s to %s"%(first,self)) - except TypeError: # first may be a ring element + raise ArithmeticError("there is no coercion from %s to %s" % (first, self)) + except TypeError: # first may be a ring element pass break if coerce: @@ -706,7 +690,7 @@ def ideal(self, *args, **kwds): g = gens[0] if len(gens) == 1: try: - g = g.gcd(g) # note: we set g = gcd(g, g) to "canonicalize" the generator: make polynomials monic, etc. + g = g.gcd(g) # note: we set g = gcd(g, g) to "canonicalize" the generator: make polynomials monic, etc. except (AttributeError, NotImplementedError): pass else: @@ -722,15 +706,15 @@ def ideal(self, *args, **kwds): gens = gens[0] return C(self, gens, **kwds) - def _ideal_class_(self,n=0): + def _ideal_class_(self, n=0): """ Return the class that is used to implement ideals of this ring. - NOTE: + .. NOTE:: - We copy the code from :class:`~sage.rings.ring.Ring`. This is - necessary because not all rings inherit from that class, such - as matrix algebras. + We copy the code from :class:`~sage.rings.ring.Ring`. This is + necessary because not all rings inherit from that class, such + as matrix algebras. INPUT: @@ -742,10 +726,10 @@ def _ideal_class_(self,n=0): The class that is used to implement ideals of this ring with ``n`` generators. - NOTE: + .. NOTE:: - Often principal ideals (``n==1``) are implemented via a different - class. + Often principal ideals (``n==1``) are implemented via + a different class. EXAMPLES:: @@ -753,7 +737,7 @@ def _ideal_class_(self,n=0): sage: MS._ideal_class_() - We don't know of a commutative ring in Sage that does not inherit + We do not know of a commutative ring in Sage that does not inherit from the base class of rings. So, we need to cheat in the next example:: @@ -765,19 +749,17 @@ def _ideal_class_(self,n=0): sage: super(Ring,QQ)._ideal_class_(2) - """ from sage.rings.noncommutative_ideals import Ideal_nc try: if not self.is_commutative(): return Ideal_nc - except (NotImplementedError,AttributeError): + except (NotImplementedError, AttributeError): return Ideal_nc from sage.rings.ideal import Ideal_generic, Ideal_principal if n == 1: return Ideal_principal - else: - return Ideal_generic + return Ideal_generic ## # Quotient rings @@ -848,9 +830,9 @@ def quo(self, I, names=None, **kwds): """ Quotient of a ring by a two-sided ideal. - NOTE: + .. NOTE:: - This is a synonym for :meth:`quotient`. + This is a synonym for :meth:`quotient`. EXAMPLES:: @@ -894,15 +876,15 @@ def quo(self, I, names=None, **kwds): sage: a == b False """ - return self.quotient(I,names=names,**kwds) + return self.quotient(I, names=names, **kwds) def quotient_ring(self, I, names=None, **kwds): """ Quotient of a ring by a two-sided ideal. - NOTE: + .. NOTE:: - This is a synonym for :meth:`quotient`. + This is a synonym for :meth:`quotient`. INPUT: @@ -980,14 +962,14 @@ def __truediv__(self, I): sage: MS/I Traceback (most recent call last): ... - TypeError: Use self.quo(I) or self.quotient(I) to construct the quotient ring. + TypeError: use self.quotient(I) to construct the quotient ring sage: QQ['x'] / ZZ Traceback (most recent call last): ... - TypeError: Use self.quo(I) or self.quotient(I) to construct the quotient ring. + TypeError: use self.quotient(I) to construct the quotient ring """ - raise TypeError("Use self.quo(I) or self.quotient(I) to construct the quotient ring.") + raise TypeError("use self.quotient(I) to construct the quotient ring") def __getitem__(self, arg): """ @@ -1153,13 +1135,12 @@ def __getitem__(self, arg): """ def normalize_arg(arg): if isinstance(arg, (tuple, list)): - # Allowing arbitrary iterables would create confusion, but we - # may want to support a few more. + # Allowing arbitrary iterables would create confusion, + # but we may want to support a few more. return tuple(arg) - elif isinstance(arg, str): + if isinstance(arg, str): return tuple(arg.split(',')) - else: - return (arg,) + return (arg,) # 1. If arg is a list, try to return a power series ring. @@ -1221,7 +1202,7 @@ def normalize_arg(arg): # right ordered ring structure. from sage.rings.real_lazy import CLF, RLF if (iv.imag().is_zero() or iv.imag().contains_zero() - and elt.imag().is_zero()): + and elt.imag().is_zero()): emb = RLF(elt) else: emb = CLF(elt) @@ -1289,11 +1270,11 @@ def free_module(self, base=None, basis=None, map=True): if basis is not None: if isinstance(basis, (list, tuple)): if len(basis) != 1: - raise ValueError("Basis must have length 1") + raise ValueError("basis must have length 1") basis = basis[0] basis = self(basis) if not basis.is_unit(): - raise ValueError("Basis element must be a unit") + raise ValueError("basis element must be a unit") from sage.modules.free_module_morphism import BaseIsomorphism1D_from_FM, BaseIsomorphism1D_to_FM Hfrom = V.Hom(self) Hto = self.Hom(V) @@ -1306,7 +1287,7 @@ def free_module(self, base=None, basis=None, map=True): raise NotImplementedError class ElementMethods: - def is_unit(self): + def is_unit(self) -> bool: r""" Return whether this element is a unit in the ring. @@ -1314,7 +1295,7 @@ def is_unit(self): This is a generic implementation for (non-commutative) rings which only works for the one element, its additive inverse, and - the zero element. Most rings should provide a more specialized + the zero element. Most rings should provide a more specialized implementation. EXAMPLES:: @@ -1329,7 +1310,7 @@ def is_unit(self): """ if self.is_one() or (-self).is_one(): return True - if self.is_zero(): # now 0 != 1 + if self.is_zero(): # now 0 != 1 return False raise NotImplementedError @@ -1369,7 +1350,6 @@ def inverse_of_unit(self): Rational Field sage: (1/a).parent() Rational Field - """ try: if not self.is_unit(): @@ -1399,8 +1379,6 @@ def _divide_if_possible(self, y): sage: _.parent() Integer Ring - :: - sage: 4._divide_if_possible(3) Traceback (most recent call last): ... @@ -1408,9 +1386,10 @@ def _divide_if_possible(self, y): """ q, r = self.quo_rem(y) if r != 0: - raise ValueError("%s is not divisible by %s"%(self, y)) + raise ValueError("%s is not divisible by %s" % (self, y)) return q + def _gen_names(elts): r""" Used to find a name for a generator when rings are created using the @@ -1419,9 +1398,9 @@ def _gen_names(elts): EXAMPLES:: sage: from sage.categories.rings import _gen_names - sage: list(_gen_names([sqrt(5)])) # optional - sage.symbolic + sage: list(_gen_names([sqrt(5)])) # optional - sage.symbolic ['sqrt5'] - sage: list(_gen_names([sqrt(-17), 2^(1/3)])) # optional - sage.symbolic + sage: list(_gen_names([sqrt(-17), 2^(1/3)])) # optional - sage.symbolic ['a', 'b'] sage: list(_gen_names((1..27)))[-1] 'aa' @@ -1430,7 +1409,7 @@ def _gen_names(elts): from sage.structure.category_object import certify_names from sage.combinat.words.words import Words it = iter(Words("abcdefghijklmnopqrstuvwxyz", infinite=False)) - next(it) # skip empty word + next(it) # skip empty word for x in elts: name = str(x) m = re.match(r'^sqrt\((\d+)\)$', name) From 854064e92ce76ad3e8c1c0ae04aa65b6bcc4e8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 9 Oct 2022 16:22:36 +0200 Subject: [PATCH 166/414] fix doctests --- src/sage/misc/sageinspect.py | 12 ++++----- src/sage/modules/free_module_morphism.py | 34 ++++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index fbca2defc20..92fc9304807 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -2144,11 +2144,11 @@ def sage_getsource(obj): - William Stein - extensions by Nick Alexander """ - #First we should check if the object has a _sage_src_ - #method. If it does, we just return the output from - #that. This is useful for getting pexpect interface - #elements to behave similar to regular Python objects - #with respect to introspection. + # First we should check if the object has a _sage_src_ + # method. If it does, we just return the output from + # that. This is useful for getting pexpect interface + # elements to behave similar to regular Python objects + # with respect to introspection. try: return obj._sage_src_() except (AttributeError, TypeError): @@ -2174,7 +2174,7 @@ def _sage_getsourcelines_name_with_dot(obj): sage: print(sage_getsource(C.parent_class)) #indirect doctest class ParentMethods: ... - Returns the Lie bracket `[x, y] = x y - y x` of `x` and `y`. + Return the Lie bracket `[x, y] = x y - y x` of `x` and `y`. ... TESTS: diff --git a/src/sage/modules/free_module_morphism.py b/src/sage/modules/free_module_morphism.py index 6a112282fa0..f7508ebeb97 100644 --- a/src/sage/modules/free_module_morphism.py +++ b/src/sage/modules/free_module_morphism.py @@ -61,6 +61,7 @@ def is_FreeModuleMorphism(x): """ return isinstance(x, FreeModuleMorphism) + class FreeModuleMorphism(matrix_morphism.MatrixMorphism): def __init__(self, parent, A, side="left"): """ @@ -80,7 +81,7 @@ def __init__(self, parent, A, side="left"): """ if not free_module_homspace.is_FreeModuleHomspace(parent): - raise TypeError("parent (=%s) must be a free module hom space"%parent) + raise TypeError("parent (=%s) must be a free module hom space" % parent) if isinstance(A, matrix_morphism.MatrixMorphism): A = A.matrix() A = parent._matrix_space(side)(A) @@ -162,15 +163,15 @@ def _repr_(self): The representation displays which side of the vectors the matrix is acting:: - sage: V = ZZ^3 - sage: h = V.hom([V.1, V.2, V.0]); h + sage: V = ZZ^3 + sage: h = V.hom([V.1, V.2, V.0]); h Free module morphism defined by the matrix [0 1 0] [0 0 1] [1 0 0] Domain: Ambient free module of rank 3 over the principal ideal domain Integer Ring Codomain: Ambient free module of rank 3 over the principal ideal domain Integer Ring - sage: h2 = V.hom([V.1, V.2, V.0], side="right"); h2 + sage: h2 = V.hom([V.1, V.2, V.0], side="right"); h2 Free module morphism defined as left-multiplication by the matrix [0 0 1] [1 0 0] @@ -551,18 +552,18 @@ def eigenvectors(self,extend=True): ], 1), (2, [ (0, 1, 0, 17/7) ], 2)] - + :: - sage: V = QQ^2 - sage: m = matrix(2, [1, 1, 0, 1]) - sage: V.hom(m, side="right").eigenvectors() + sage: V = QQ^2 + sage: m = matrix(2, [1, 1, 0, 1]) + sage: V.hom(m, side="right").eigenvectors() [(1, [ (1, 0) ], 2)] - sage: V.hom(m).eigenvectors() + sage: V.hom(m).eigenvectors() [(1, [ (0, 1) @@ -638,16 +639,16 @@ def eigenspaces(self,extend=True): Basis matrix: [0 1 0] [0 0 1])] - + :: - sage: V = QQ^2; m = matrix(2, [1, 1, 0, 1]) - sage: V.hom(m, side="right").eigenspaces() + sage: V = QQ^2; m = matrix(2, [1, 1, 0, 1]) + sage: V.hom(m, side="right").eigenspaces() [(1, Vector space of degree 2 and dimension 1 over Rational Field Basis matrix: [1 0])] - sage: V.hom(m).eigenspaces() + sage: V.hom(m).eigenspaces() [(1, Vector space of degree 2 and dimension 1 over Rational Field Basis matrix: @@ -796,7 +797,7 @@ class BaseIsomorphism1D_to_FM(BaseIsomorphism1D): sage: W, from_W, to_W = R.free_module(R, basis=4) Traceback (most recent call last): ... - ValueError: Basis element must be a unit + ValueError: basis element must be a unit """ def __init__(self, parent, basis=None): """ @@ -852,7 +853,7 @@ class BaseIsomorphism1D_from_FM(BaseIsomorphism1D): sage: W, from_W, to_W = R.free_module(R, basis=x) Traceback (most recent call last): ... - ValueError: Basis element must be a unit + ValueError: basis element must be a unit """ def __init__(self, parent, basis=None): """ @@ -878,5 +879,4 @@ def _call_(self, x): """ if self._basis is None: return x[0] - else: - return self.codomain()(x[0] / self._basis) + return self.codomain()(x[0] / self._basis) From 68990ba48ede32ed838a1c6e2567e5eaf1d7abaa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Oct 2022 11:09:44 -0700 Subject: [PATCH 167/414] build/pkgs/scipy: Update to 1.9.2 --- build/pkgs/scipy/checksums.ini | 6 +++--- build/pkgs/scipy/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/scipy/checksums.ini b/build/pkgs/scipy/checksums.ini index 83f9a385fca..086694d7214 100644 --- a/build/pkgs/scipy/checksums.ini +++ b/build/pkgs/scipy/checksums.ini @@ -1,5 +1,5 @@ tarball=scipy-VERSION.tar.gz -sha1=2f5a7bb5f992dd6766a3a0e088180be427518903 -md5=e6e70a9014dba74b4ef16686d23fd3ad -cksum=39501961 +sha1=f93163946802657fcd1c10821a1330497045ef6b +md5=ee6db269d03b2d47d04e876d38515d0d +cksum=3886027417 upstream_url=https://pypi.io/packages/source/s/scipy/scipy-VERSION.tar.gz diff --git a/build/pkgs/scipy/package-version.txt b/build/pkgs/scipy/package-version.txt index 9ab8337f396..8fdcf386946 100644 --- a/build/pkgs/scipy/package-version.txt +++ b/build/pkgs/scipy/package-version.txt @@ -1 +1 @@ -1.9.1 +1.9.2 From 4df3e8c32c5e07447b57d7c03a55059d38cd8fc9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 9 Oct 2022 20:14:51 -0700 Subject: [PATCH 168/414] build/pkgs/meson: Update to 0.63.3 --- build/pkgs/meson/checksums.ini | 6 +++--- build/pkgs/meson/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/meson/checksums.ini b/build/pkgs/meson/checksums.ini index c14db7a0114..39f0a42c527 100644 --- a/build/pkgs/meson/checksums.ini +++ b/build/pkgs/meson/checksums.ini @@ -1,5 +1,5 @@ tarball=meson-VERSION.tar.gz -sha1=e81d2915173324476693e585a785abed8fd1bbad -md5=078e59d11a72b74c3bd78cb8205e9ed7 -cksum=4240866935 +sha1=3bce963302f547547c82fda35f84838ebc608e8a +md5=b2f2757b5dd84cc754b9df53ce37a175 +cksum=2257545181 upstream_url=https://pypi.io/packages/source/m/meson/meson-VERSION.tar.gz diff --git a/build/pkgs/meson/package-version.txt b/build/pkgs/meson/package-version.txt index 630f2e0ce67..068337d8307 100644 --- a/build/pkgs/meson/package-version.txt +++ b/build/pkgs/meson/package-version.txt @@ -1 +1 @@ -0.63.1 +0.63.3 From 77e77c77e73df867bfb25ef3b220a50af4e3f0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 10 Oct 2022 17:26:10 +0200 Subject: [PATCH 169/414] tiny details, use transmute --- src/sage/combinat/triangles_FHM.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/triangles_FHM.py b/src/sage/combinat/triangles_FHM.py index 1f9f830ef9b..35eccbd0046 100644 --- a/src/sage/combinat/triangles_FHM.py +++ b/src/sage/combinat/triangles_FHM.py @@ -34,7 +34,6 @@ """ from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.polynomial_ring import polygen from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.structure.sage_object import SageObject @@ -320,7 +319,7 @@ def dual(self): for (dx, dy), coeff in self._poly.dict().items()} return M_triangle(A(dict_dual), variables=(x, y)) - def transpose(self): + def transmute(self): """ Return the image of ``self`` by an involution. @@ -337,9 +336,9 @@ def transpose(self): sage: x, y = polygens(ZZ, 'x,y') sage: nc3 = x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 sage: m = M_triangle(nc3) - sage: m2 = m.transpose(); m2 + sage: m2 = m.transmute(); m2 2*x^2*y^2 - 3*x*y^2 + 2*x*y + y^2 - 2*y + 1 - sage: m2.transpose() == m + sage: m2.transmute() == m True """ return self.h().transpose().m() @@ -622,7 +621,6 @@ def vector(self): return anneau(self._poly(y=x)) - class Gamma_triangle(Triangle): """ Class for the Gamma-triangles. From 518b0bf598809626e13eef35fbc58da6e004979c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Oct 2022 08:50:34 +0200 Subject: [PATCH 170/414] some suggestions --- src/sage/combinat/triangles_FHM.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/triangles_FHM.py b/src/sage/combinat/triangles_FHM.py index 35eccbd0046..396f299c46f 100644 --- a/src/sage/combinat/triangles_FHM.py +++ b/src/sage/combinat/triangles_FHM.py @@ -13,6 +13,11 @@ sage: posets.NoncrossingPartitions(W).M_triangle() x^3*y^3 - 6*x^2*y^3 + 6*x^2*y^2 + 10*x*y^3 - 16*x*y^2 - 5*y^3 + 6*x*y + 10*y^2 - 6*y + 1 + sage: unicode_art(_) + ⎛ -5 10 -6 1⎞ + ⎜ 10 -16 6 0⎟ + ⎜ -6 6 0 0⎟ + ⎝ 1 0 0 0⎠ The F-triangle class is motivated by the generating series of pure simplicial complexes endowed with a distinguished facet. One can also @@ -23,6 +28,11 @@ sage: f = C.greedy_facet() sage: C.F_triangle(f) 5*x^3 + 5*x^2*y + 3*x*y^2 + y^3 + 10*x^2 + 8*x*y + 3*y^2 + 6*x + 3*y + 1 + sage: unicode_art(_) + ⎛ 1 0 0 0⎞ + ⎜ 3 3 0 0⎟ + ⎜ 3 8 5 0⎟ + ⎝ 1 6 10 5⎠ The H-triangles are related to the F-triangles by a relationship similar to the classical link between the f-vector and the h-vector of a @@ -40,7 +50,7 @@ def _matrix_display(self, variables=None): """ - Return the 2-variables polynomial ``self`` as a matrix for display. + Return the 2-variable polynomial ``self`` as a matrix for display. INPUT: @@ -79,9 +89,9 @@ def _matrix_display(self, variables=None): else: x, y = variables ring = self.parent() - toutes_vars = x.parent().gens() - ix = toutes_vars.index(x) - iy = toutes_vars.index(y) + all_vars = x.parent().gens() + ix = all_vars.index(x) + iy = all_vars.index(y) minx = min(u[ix] for u in support) maxy = max(u[iy] for u in support) deltax = max(u[ix] for u in support) - minx + 1 From 2fbf8489733d8c7c052cd5c1a59ee029c8c2bb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Oct 2022 08:52:36 +0200 Subject: [PATCH 171/414] one more detail --- src/sage/topology/simplicial_complex.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index 6b07c7aef5d..7d194024016 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -1584,7 +1584,8 @@ def h_triangle(self): def F_triangle(self, S): """ - Return the F-triangle of ``self`` w.r.t one maximal simplex ``S``. + Return the F-triangle of ``self`` with respect + to one maximal simplex ``S``. This is the bivariate generating polynomial of all faces, according to the number of elements in ``S`` and outside ``S``. From 0de22b101de7f9a790e8cb043350760624c1a936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Oct 2022 10:38:03 +0200 Subject: [PATCH 172/414] fix the doc (using trac role) --- src/sage/categories/rings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index d43dc15bbb1..6a7589bb109 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -49,7 +49,7 @@ class Rings(CategoryWithAxiom): .. TODO:: - (see: https://trac.sagemath.org/sage_trac/wiki/CategoriesRoadMap) + (see :trac:`sage_trac/wiki/CategoriesRoadMap`) - Make Rings() into a subcategory or alias of Algebras(ZZ); From 35e42a1994f8dff36815e01949fd20bf0bb133bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Oct 2022 11:21:47 +0200 Subject: [PATCH 173/414] changing the repr --- src/sage/combinat/posets/posets.py | 2 +- src/sage/combinat/triangles_FHM.py | 86 ++++++++++++++----------- src/sage/topology/simplicial_complex.py | 3 +- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 025c34bdcf9..48eadee2fa2 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -7280,7 +7280,7 @@ def M_triangle(self): sage: P = posets.DiamondPoset(5) sage: P.M_triangle() - x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 + M: x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 TESTS:: diff --git a/src/sage/combinat/triangles_FHM.py b/src/sage/combinat/triangles_FHM.py index 396f299c46f..6e962476433 100644 --- a/src/sage/combinat/triangles_FHM.py +++ b/src/sage/combinat/triangles_FHM.py @@ -11,8 +11,8 @@ sage: W = SymmetricGroup(4) sage: posets.NoncrossingPartitions(W).M_triangle() - x^3*y^3 - 6*x^2*y^3 + 6*x^2*y^2 + 10*x*y^3 - 16*x*y^2 - 5*y^3 - + 6*x*y + 10*y^2 - 6*y + 1 + M: x^3*y^3 - 6*x^2*y^3 + 6*x^2*y^2 + 10*x*y^3 - 16*x*y^2 + - 5*y^3 + 6*x*y + 10*y^2 - 6*y + 1 sage: unicode_art(_) ⎛ -5 10 -6 1⎞ ⎜ 10 -16 6 0⎟ @@ -27,7 +27,7 @@ sage: C = ClusterComplex(['A',3]) sage: f = C.greedy_facet() sage: C.F_triangle(f) - 5*x^3 + 5*x^2*y + 3*x*y^2 + y^3 + 10*x^2 + 8*x*y + 3*y^2 + 6*x + 3*y + 1 + F: 5*x^3 + 5*x^2*y + 3*x*y^2 + y^3 + 10*x^2 + 8*x*y + 3*y^2 + 6*x + 3*y + 1 sage: unicode_art(_) ⎛ 1 0 0 0⎞ ⎜ 3 3 0 0⎟ @@ -168,9 +168,9 @@ def _repr_(self) -> str: sage: x, y = polygens(ZZ, 'x,y') sage: ht = H_triangle(1+2*x*y) sage: ht - 2*x*y + 1 + H: 2*x*y + 1 """ - return repr(self._poly) + return self._prefix + repr(self._poly) def __eq__(self, other) -> bool: """ @@ -283,7 +283,7 @@ def truncate(self, d): sage: x, y = polygens(ZZ, 'x,y') sage: h = H_triangle(1+2*x*y) sage: h.truncate(2) - 2*x*y + 1 + H: 2*x*y + 1 """ p = self._poly for v in self._vars: @@ -302,8 +302,10 @@ class M_triangle(Triangle): sage: x, y = polygens(ZZ, 'x,y') sage: P = Poset({2:[1]}) sage: P.M_triangle() - x*y - y + 1 + M: x*y - y + 1 """ + _prefix = 'M: ' + def dual(self): """ Return the dual M-triangle. @@ -347,7 +349,7 @@ def transmute(self): sage: nc3 = x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 sage: m = M_triangle(nc3) sage: m2 = m.transmute(); m2 - 2*x^2*y^2 - 3*x*y^2 + 2*x*y + y^2 - 2*y + 1 + M: 2*x^2*y^2 - 3*x*y^2 + 2*x*y + y^2 - 2*y + 1 sage: m2.transmute() == m True """ @@ -362,7 +364,7 @@ def h(self): sage: from sage.combinat.triangles_FHM import M_triangle sage: x, y = polygens(ZZ,'x,y') sage: M_triangle(1-y+x*y).h() - x*y + 1 + H: x*y + 1 TESTS:: @@ -370,7 +372,7 @@ def h(self): sage: x, y = polygens(h.parent(),'x,y') sage: mt = x**2*y**2+(-2*h+2)*x*y**2+(2*h-2)*x*y+(2*h-3)*y**2+(-2*h+2)*y+1 sage: M_triangle(mt, [x,y]).h() - x^2*y^2 + 2*x*y + (2*h - 4)*x + 1 + H: x^2*y^2 + 2*x*y + (2*h - 4)*x + 1 """ x, y = self._vars n = self._n @@ -388,7 +390,7 @@ def f(self): sage: from sage.combinat.triangles_FHM import M_triangle sage: x, y = polygens(ZZ,'x,y') sage: M_triangle(1-y+x*y).f() - x + y + 1 + F: x + y + 1 TESTS:: @@ -396,7 +398,7 @@ def f(self): sage: x, y = polygens(h.parent(),'x,y') sage: mt = x**2*y**2+(-2*h+2)*x*y**2+(2*h-2)*x*y+(2*h-3)*y**2+(-2*h+2)*y+1 sage: M_triangle(mt, [x,y]).f() - (2*h - 3)*x^2 + 2*x*y + y^2 + (2*h - 2)*x + 2*y + 1 + F: (2*h - 3)*x^2 + 2*x*y + y^2 + (2*h - 2)*x + 2*y + 1 """ return self.h().f() @@ -405,6 +407,8 @@ class H_triangle(Triangle): """ Class for the H-triangles. """ + _prefix = 'H: ' + def transpose(self): """ Return the transposed H-triangle. @@ -422,9 +426,9 @@ def transpose(self): sage: from sage.combinat.triangles_FHM import H_triangle sage: x, y = polygens(ZZ,'x,y') sage: H_triangle(1+x*y).transpose() - x*y + 1 + H: x*y + 1 sage: H_triangle(x^2*y^2 + 2*x*y + x + 1).transpose() - x^2*y^2 + x^2*y + 2*x*y + 1 + H: x^2*y^2 + x^2*y + 2*x*y + 1 """ x, y = self._vars n = self._n @@ -445,8 +449,8 @@ def m(self): sage: x, y = polygens(h.parent(),'x,y') sage: ht = H_triangle(x^2*y^2 + 2*x*y + 2*x*h - 4*x + 1, variables=[x,y]) sage: ht.m() - x^2*y^2 + (-2*h + 2)*x*y^2 + (2*h - 2)*x*y + (2*h - 3)*y^2 + - (-2*h + 2)*y + 1 + M: x^2*y^2 + (-2*h + 2)*x*y^2 + (2*h - 2)*x*y + + (2*h - 3)*y^2 + (-2*h + 2)*y + 1 """ x, y = self._vars n = self._n @@ -463,13 +467,14 @@ def f(self): sage: from sage.combinat.triangles_FHM import H_triangle sage: x, y = polygens(ZZ,'x,y') sage: H_triangle(1+x*y).f() - x + y + 1 + F: x + y + 1 sage: H_triangle(x^2*y^2 + 2*x*y + x + 1).f() - 2*x^2 + 2*x*y + y^2 + 3*x + 2*y + 1 + F: 2*x^2 + 2*x*y + y^2 + 3*x + 2*y + 1 sage: flo = H_triangle(1+4*x+2*x**2+x*y*(4+8*x)+ ....: x**2*y**2*(6+4*x)+4*(x*y)**3+(x*y)**4).f(); flo - 7*x^4 + 12*x^3*y + 10*x^2*y^2 + 4*x*y^3 + y^4 + 20*x^3 + 28*x^2*y - + 16*x*y^2 + 4*y^3 + 20*x^2 + 20*x*y + 6*y^2 + 8*x + 4*y + 1 + F: 7*x^4 + 12*x^3*y + 10*x^2*y^2 + 4*x*y^3 + y^4 + 20*x^3 + + 28*x^2*y + 16*x*y^2 + 4*y^3 + 20*x^2 + 20*x*y + + 6*y^2 + 8*x + 4*y + 1 sage: flo(-1-x,-1-y) == flo True @@ -478,7 +483,7 @@ def f(self): sage: x,y,h = polygens(ZZ,'x,y,h') sage: ht = x^2*y^2 + 2*x*y + 2*x*h - 4*x + 1 sage: H_triangle(ht,[x,y]).f() - 2*x^2*h - 3*x^2 + 2*x*y + y^2 + 2*x*h - 2*x + 2*y + 1 + F: 2*x^2*h - 3*x^2 + 2*x*y + y^2 + 2*x*h - 2*x + 2*y + 1 """ x, y = self._vars n = self._n @@ -500,12 +505,12 @@ def gamma(self): sage: x, y = polygen(ZZ,'x,y') sage: ht = x**2*y**2 + 2*x*y + x + 1 sage: H_triangle(ht).gamma() - y^2 + x + Γ: y^2 + x sage: W = SymmetricGroup(5) sage: P = posets.NoncrossingPartitions(W) sage: P.M_triangle().h().gamma() - y^4 + 3*x*y^2 + 2*x^2 + 2*x*y + x + Γ: y^4 + 3*x*y^2 + 2*x^2 + 2*x*y + x """ x, y = self._vars n = self._n @@ -539,6 +544,8 @@ class F_triangle(Triangle): """ Class for the F-triangles. """ + _prefix = 'F: ' + def h(self): """ Return the associated H-triangle. @@ -549,7 +556,7 @@ def h(self): sage: x,y = polygens(ZZ,'x,y') sage: ft = F_triangle(1+x+y) sage: ft.h() - x*y + 1 + H: x*y + 1 TESTS:: @@ -557,7 +564,7 @@ def h(self): sage: x, y = polygens(h.parent(),'x,y') sage: ft = 1+2*y+(2*h-2)*x+y**2+2*x*y+(2*h-3)*x**2 sage: F_triangle(ft, [x,y]).h() - x^2*y^2 + 2*x*y + (2*h - 4)*x + 1 + H: x^2*y^2 + 2*x*y + (2*h - 4)*x + 1 """ x, y = self._vars n = self._n @@ -574,36 +581,35 @@ def m(self): sage: from sage.combinat.triangles_FHM import H_triangle sage: x, y = polygens(ZZ,'x,y') sage: H_triangle(1+x*y).f() - x + y + 1 + F: x + y + 1 sage: _.m() - x*y - y + 1 + M: x*y - y + 1 sage: H_triangle(x^2*y^2 + 2*x*y + x + 1).f() - 2*x^2 + 2*x*y + y^2 + 3*x + 2*y + 1 + F: 2*x^2 + 2*x*y + y^2 + 3*x + 2*y + 1 sage: _.m() - x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 + M: x^2*y^2 - 3*x*y^2 + 3*x*y + 2*y^2 - 3*y + 1 TESTS:: sage: p = 1+4*x+2*x**2+x*y*(4+8*x) sage: p += x**2*y**2*(6+4*x)+4*(x*y)**3+(x*y)**4 sage: flo = H_triangle(p).f(); flo - 7*x^4 + 12*x^3*y + 10*x^2*y^2 + 4*x*y^3 + y^4 + 20*x^3 - + 28*x^2*y + 16*x*y^2 + 4*y^3 + 20*x^2 + 20*x*y + 6*y^2 - + 8*x + 4*y + 1 + F: 7*x^4 + 12*x^3*y + 10*x^2*y^2 + 4*x*y^3 + y^4 + 20*x^3 + + 28*x^2*y + 16*x*y^2 + 4*y^3 + 20*x^2 + 20*x*y + + 6*y^2 + 8*x + 4*y + 1 sage: flo.m() - x^4*y^4 - 8*x^3*y^4 + 8*x^3*y^3 + 20*x^2*y^4 - - 36*x^2*y^3 - 20*x*y^4 - + 16*x^2*y^2 + 48*x*y^3 + 7*y^4 - 36*x*y^2 - 20*y^3 + 8*x*y - + 20*y^2 - 8*y + 1 + M: x^4*y^4 - 8*x^3*y^4 + 8*x^3*y^3 + 20*x^2*y^4 - 36*x^2*y^3 + - 20*x*y^4 + 16*x^2*y^2 + 48*x*y^3 + 7*y^4 - 36*x*y^2 - 20*y^3 + + 8*x*y + 20*y^2 - 8*y + 1 sage: from sage.combinat.triangles_FHM import F_triangle sage: h = polygen(ZZ, 'h') sage: x, y = polygens(h.parent(),'x,y') sage: ft = F_triangle(1+2*y+(2*h-2)*x+y**2+2*x*y+(2*h-3)*x**2,(x,y)) sage: ft.m() - x^2*y^2 + (-2*h + 2)*x*y^2 + (2*h - 2)*x*y + (2*h - 3)*y^2 - + (-2*h + 2)*y + 1 + M: x^2*y^2 + (-2*h + 2)*x*y^2 + (2*h - 2)*x*y + + (2*h - 3)*y^2 + (-2*h + 2)*y + 1 """ x, y = self._vars n = self._n @@ -635,6 +641,8 @@ class Gamma_triangle(Triangle): """ Class for the Gamma-triangles. """ + _prefix = 'Γ: ' + def h(self): r""" Return the associated H-triangle. @@ -652,7 +660,7 @@ def h(self): sage: x, y = polygen(ZZ,'x,y') sage: g = y**2 + x sage: Gamma_triangle(g).h() - x^2*y^2 + 2*x*y + x + 1 + H: x^2*y^2 + 2*x*y + x + 1 sage: a, b = polygen(ZZ, 'a, b') sage: x, y = polygens(a.parent(),'x,y') diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index 7d194024016..84026ddb17e 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -1602,7 +1602,8 @@ def F_triangle(self, S): sage: cs = simplicial_complexes.Torus() sage: cs.F_triangle(cs.facets()[0]) - x^3 + 9*x^2*y + 3*x*y^2 + y^3 + 6*x^2 + 12*x*y + 3*y^2 + 4*x + 3*y + 1 + F: x^3 + 9*x^2*y + 3*x*y^2 + y^3 + 6*x^2 + 12*x*y + + 3*y^2 + 4*x + 3*y + 1 """ x, y = polygens(ZZ, 'x, y') from sage.combinat.triangles_FHM import F_triangle From 8b063278be4a12ef00444fa10563919128c83152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 11 Oct 2022 11:26:13 +0200 Subject: [PATCH 174/414] fix typo --- src/sage/combinat/posets/posets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 48eadee2fa2..2836d59d960 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -7270,7 +7270,7 @@ def M_triangle(self): an :class:`~sage.combinat.triangles_FHM.M_triangle` - Thie M-triangle is the generating polynomial of the Möbius numbers + The M-triangle is the generating polynomial of the Möbius numbers .. MATH:: From 814aa7cd53e8ca569ddfd87ba1cfa498c564531d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 11 Oct 2022 12:11:27 +0200 Subject: [PATCH 175/414] remove Stream_cauchy_invert.get_coefficient, make sparse a mandatory argument, move _is_sparse an attribute of Stream_inexact --- src/sage/data_structures/stream.py | 261 +++++++++++++---------------- src/sage/rings/lazy_series.py | 40 +++-- src/sage/rings/lazy_series_ring.py | 6 +- 3 files changed, 144 insertions(+), 163 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 2bbb2f99106..a8d9f8f05d9 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -59,19 +59,19 @@ There is a unary negation operator:: - sage: h = Stream_neg(f) + sage: h = Stream_neg(f, True) sage: [h[i] for i in range(10)] [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] More generally, we can multiply by a scalar:: - sage: h = Stream_lmul(f, 2) + sage: h = Stream_lmul(f, 2, True) sage: [h[i] for i in range(10)] [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] Finally, we can apply an arbitrary functions to the elements of a stream:: - sage: h = Stream_map_coefficients(f, lambda n: n^2) + sage: h = Stream_map_coefficients(f, lambda n: n^2, True) sage: [h[i] for i in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] @@ -110,7 +110,6 @@ class Stream(): INPUT: - - ``sparse`` -- boolean; whether the implementation of the stream is sparse - ``true_order`` -- boolean; if the approximate order is the actual order .. NOTE:: @@ -133,16 +132,15 @@ class Stream(): value before it is accessed. """ - def __init__(self, sparse, true_order): + def __init__(self, true_order): """ Initialize ``self``. EXAMPLES:: sage: from sage.data_structures.stream import Stream - sage: CS = Stream(True, 1) + sage: CS = Stream(1) """ - self._is_sparse = sparse self._true_order = true_order @lazy_attribute @@ -169,10 +167,10 @@ def __ne__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import Stream - sage: CS = Stream(True, 1) + sage: CS = Stream(1) sage: CS != CS False - sage: CS != Stream(False, -2) + sage: CS != Stream(-2) False """ return False @@ -187,7 +185,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream - sage: CS = Stream(True, 1) + sage: CS = Stream(1) sage: CS.is_nonzero() False """ @@ -199,11 +197,12 @@ class Stream_inexact(Stream): An abstract base class for the stream when we do not know it is eventually constant. + In particular, a cache is provided. + INPUT: - - ``sparse`` -- boolean; whether the implementation of the stream is sparse - - ``approximate_order`` -- integer; a lower bound for the order - of the stream + - ``is_sparse`` -- boolean; whether the implementation of the stream is sparse + - ``true_order`` -- boolean; if the approximate order is the actual order .. TODO:: @@ -228,7 +227,8 @@ def __init__(self, is_sparse, true_order): True """ - super().__init__(is_sparse, true_order) + super().__init__(true_order) + self._is_sparse = is_sparse if self._is_sparse: self._cache = dict() # cache of known coefficients else: @@ -680,7 +680,7 @@ def __init__(self, initial_coefficients, constant=None, degree=None, order=None) assert self._initial_coefficients or self._constant, "Stream_exact should only be used for non-zero streams" - super().__init__(None, True) + super().__init__(True) self._approximate_order = order def __getitem__(self, n): @@ -1068,7 +1068,7 @@ class Stream_unary(Stream_inexact): sage: g = Stream_cauchy_invert(f) sage: [g[i] for i in range(10)] [-1, 1/2, 0, 0, 0, 0, 0, 0, 0, 0] - sage: g = Stream_lmul(f, 2) + sage: g = Stream_lmul(f, 2, True) sage: [g[i] for i in range(10)] [0, 4, 8, 12, 16, 20, 24, 28, 32, 36] """ @@ -1117,8 +1117,8 @@ def __eq__(self, other): sage: from sage.data_structures.stream import (Stream_function, Stream_rmul) sage: f = Stream_function(lambda n: 2*n, False, 1) sage: g = Stream_function(lambda n: n, False, 1) - sage: h = Stream_rmul(f, 2) - sage: n = Stream_rmul(g, 2) + sage: h = Stream_rmul(f, 2, True) + sage: n = Stream_rmul(g, 2, True) sage: h == n False sage: n == n @@ -1286,34 +1286,26 @@ class Stream_zero(Stream): """ A coefficient stream that is exactly equal to zero. - INPUT: - - - ``sparse`` -- boolean; whether the coefficient stream is sparse or not - EXAMPLES:: sage: from sage.data_structures.stream import Stream_zero - sage: s = Stream_zero(True) + sage: s = Stream_zero() sage: s[5] 0 """ - def __init__(self, is_sparse): + def __init__(self): """ Initialize ``self``. TESTS:: sage: from sage.data_structures.stream import Stream_zero - sage: s = Stream_zero(False) + sage: s = Stream_zero() sage: TestSuite(s).run() - .. TODO:: - - Having ``is_sparse`` as argument here does not really - make sense, since this stream does not cache values. """ - super().__init__(is_sparse, True) + super().__init__(True) self._approximate_order = infinity def __getitem__(self, n): @@ -1327,7 +1319,7 @@ def __getitem__(self, n): EXAMPLES:: sage: from sage.data_structures.stream import Stream_zero - sage: s = Stream_zero(True) + sage: s = Stream_zero() sage: s[1] 0 sage: sum([s[i] for i in range(10)]) @@ -1342,7 +1334,7 @@ def order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_zero - sage: s = Stream_zero(True) + sage: s = Stream_zero() sage: s.order() +Infinity """ @@ -1350,12 +1342,12 @@ def order(self): def __eq__(self, other): """ - Check equality of ``self`` and ``other`` ignoring sparsity. + Check equality of ``self`` and ``other``. EXAMPLES:: sage: from sage.data_structures.stream import Stream_zero - sage: Stream_zero(True) == Stream_zero(False) + sage: Stream_zero() == Stream_zero() True """ return self is other or isinstance(other, Stream_zero) @@ -1367,14 +1359,9 @@ def __hash__(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_zero - sage: s = Stream_zero(False) - sage: a = hash(s); a + sage: s = Stream_zero() + sage: hash(s) 0 - sage: t = Stream_zero(False) - sage: b = hash(t); b - 0 - sage: b == a - True """ return 0 @@ -1745,13 +1732,13 @@ class Stream_dirichlet_invert(Stream_unary): sage: from sage.data_structures.stream import (Stream_dirichlet_invert, Stream_function) sage: f = Stream_function(lambda n: 1, True, 1) - sage: g = Stream_dirichlet_invert(f) + sage: g = Stream_dirichlet_invert(f, True) sage: [g[i] for i in range(10)] [0, 1, -1, -1, 0, -1, 1, -1, 0, 0] sage: [moebius(i) for i in range(10)] [0, 1, -1, -1, 0, -1, 1, -1, 0, 0] """ - def __init__(self, series): + def __init__(self, series, is_sparse): """ Initialize. @@ -1759,13 +1746,13 @@ def __init__(self, series): sage: from sage.data_structures.stream import (Stream_exact, Stream_dirichlet_invert) sage: f = Stream_exact([0, 0], constant=1) - sage: g = Stream_dirichlet_invert(f) + sage: g = Stream_dirichlet_invert(f, True) sage: g[1] Traceback (most recent call last): ... ZeroDivisionError: the Dirichlet inverse only exists if the coefficient with index 1 is non-zero """ - super().__init__(series, series._is_sparse) + super().__init__(series, is_sparse) self._zero = ZZ.zero() @lazy_attribute @@ -1777,7 +1764,7 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_dirichlet_invert sage: f = Stream_function(lambda n: n, True, 1) - sage: h = Stream_dirichlet_invert(f) + sage: h = Stream_dirichlet_invert(f, True) sage: h._approximate_order 1 sage: [h[i] for i in range(5)] @@ -1799,12 +1786,12 @@ def _ainv(self): sage: from sage.data_structures.stream import (Stream_exact, Stream_dirichlet_invert) sage: f = Stream_exact([0, 3], constant=2) - sage: g = Stream_dirichlet_invert(f) + sage: g = Stream_dirichlet_invert(f, True) sage: g._ainv 1/3 sage: f = Stream_exact([Zmod(6)(5)], constant=2, order=1) - sage: g = Stream_dirichlet_invert(f) + sage: g = Stream_dirichlet_invert(f, True) sage: g._ainv 5 """ @@ -1825,7 +1812,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_exact, Stream_dirichlet_invert) sage: f = Stream_exact([0, 3], constant=2) - sage: g = Stream_dirichlet_invert(f) + sage: g = Stream_dirichlet_invert(f, True) sage: g.get_coefficient(6) 2/27 sage: [g[i] for i in range(8)] @@ -1908,7 +1895,7 @@ def _approximate_order(self): ginv = Stream_cauchy_invert(self._right) # The constant part makes no contribution to the negative. # We need this for the case so self._neg_powers[0][n] => 0. - self._neg_powers = [Stream_zero(self._left._is_sparse), ginv] + self._neg_powers = [Stream_zero(), ginv] for i in range(1, -self._left._approximate_order): # TODO: possibly we always want a dense cache here? self._neg_powers.append(Stream_cauchy_mul(self._neg_powers[-1], ginv, self._is_sparse)) @@ -2064,7 +2051,7 @@ def __init__(self, f, g, is_sparse, p, ring=None, include=None, exclude=None): else: self._basis = ring self._p = p - g = Stream_map_coefficients(g, lambda x: p(x)) + g = Stream_map_coefficients(g, lambda x: p(x), is_sparse) self._powers = [g] # a cache for the powers of g R = self._basis.base_ring() self._degree_one = _variables_recursive(R, include=include, exclude=exclude) @@ -2072,11 +2059,11 @@ def __init__(self, f, g, is_sparse, p, ring=None, include=None, exclude=None): if HopfAlgebrasWithBasis(R).TensorProducts() in p.categories(): self._tensor_power = len(p._sets) p_f = p._sets[0] - f = Stream_map_coefficients(f, lambda x: p_f(x)) + f = Stream_map_coefficients(f, lambda x: p_f(x), is_sparse) else: self._tensor_power = None - f = Stream_map_coefficients(f, lambda x: p(x)) - super().__init__(f, g, f._is_sparse) + f = Stream_map_coefficients(f, lambda x: p(x), is_sparse) + super().__init__(f, g, is_sparse) @lazy_attribute def _approximate_order(self): @@ -2266,7 +2253,7 @@ class Stream_scalar(Stream_inexact): :meth:`Stream_unary.__eq__`. Would this be any better? """ - def __init__(self, series, scalar): + def __init__(self, series, scalar, is_sparse): """ Initialize ``self``. @@ -2274,12 +2261,12 @@ def __init__(self, series, scalar): sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) sage: f = Stream_function(lambda n: -1, True, 0) - sage: g = Stream_rmul(f, 3) + sage: g = Stream_rmul(f, 3, True) """ self._series = series self._scalar = scalar assert scalar, "the scalar must not be equal to 0" - super().__init__(series._is_sparse, series._true_order) + super().__init__(is_sparse, series._true_order) @lazy_attribute def _approximate_order(self): @@ -2290,7 +2277,7 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_rmul sage: f = Stream_function(lambda n: Zmod(6)(n), True, 2) - sage: h = Stream_rmul(f, 3) # indirect doctest + sage: h = Stream_rmul(f, 3, True) # indirect doctest sage: h._approximate_order 2 sage: [h[i] for i in range(5)] @@ -2308,7 +2295,7 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_function sage: from sage.data_structures.stream import Stream_rmul sage: a = Stream_function(lambda n: 2*n, False, 1) - sage: f = Stream_rmul(a, 2) + sage: f = Stream_rmul(a, 2, True) sage: hash(f) == hash(f) True """ @@ -2328,14 +2315,14 @@ def __eq__(self, other): sage: from sage.data_structures.stream import Stream_rmul, Stream_lmul sage: a = Stream_function(lambda n: 2*n, False, 1) sage: b = Stream_function(lambda n: n, False, 1) - sage: f = Stream_rmul(a, 2) - sage: f == Stream_rmul(b, 2) + sage: f = Stream_rmul(a, 2, True) + sage: f == Stream_rmul(b, 2, True) False - sage: f == Stream_rmul(a, 2) + sage: f == Stream_rmul(a, 2, False) True - sage: f == Stream_rmul(a, 3) + sage: f == Stream_rmul(a, 3, True) False - sage: f == Stream_lmul(a, 3) + sage: f == Stream_lmul(a, 3, True) False """ return (isinstance(other, type(self)) and self._series == other._series @@ -2350,13 +2337,13 @@ def is_nonzero(self): sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_rmul(f, 2) + sage: g = Stream_rmul(f, 2, True) sage: g.is_nonzero() False sage: from sage.data_structures.stream import Stream_cauchy_invert sage: fi = Stream_cauchy_invert(f) - sage: g = Stream_rmul(fi, 2) + sage: g = Stream_rmul(fi, 2, True) sage: g.is_nonzero() True """ @@ -2379,7 +2366,7 @@ class Stream_rmul(Stream_scalar): sage: W = algebras.DifferentialWeyl(QQ, names=('x',)) sage: x, dx = W.gens() sage: f = Stream_function(lambda n: x^n, True, 1) - sage: g = Stream_rmul(f, dx) + sage: g = Stream_rmul(f, dx, True) sage: [g[i] for i in range(5)] [0, x*dx + 1, x^2*dx + 2*x, x^3*dx + 3*x^2, x^4*dx + 4*x^3] """ @@ -2395,7 +2382,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_rmul(f, 3) + sage: g = Stream_rmul(f, 3, True) sage: g.get_coefficient(5) 15 sage: [g.get_coefficient(i) for i in range(10)] @@ -2420,7 +2407,7 @@ class Stream_lmul(Stream_scalar): sage: W = algebras.DifferentialWeyl(QQ, names=('x',)) sage: x, dx = W.gens() sage: f = Stream_function(lambda n: x^n, True, 1) - sage: g = Stream_lmul(f, dx) + sage: g = Stream_lmul(f, dx, True) sage: [g[i] for i in range(5)] [0, x*dx, x^2*dx, x^3*dx, x^4*dx] """ @@ -2436,7 +2423,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_lmul, Stream_function) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_lmul(f, 3) + sage: g = Stream_lmul(f, 3, True) sage: g.get_coefficient(5) 15 sage: [g.get_coefficient(i) for i in range(10)] @@ -2457,11 +2444,11 @@ class Stream_neg(Stream_unary): sage: from sage.data_structures.stream import (Stream_neg, Stream_function) sage: f = Stream_function(lambda n: 1, True, 1) - sage: g = Stream_neg(f) + sage: g = Stream_neg(f, True) sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] """ - def __init__(self, series): + def __init__(self, series, is_sparse): """ Initialize ``self``. @@ -2469,9 +2456,9 @@ def __init__(self, series): sage: from sage.data_structures.stream import (Stream_neg, Stream_function) sage: f = Stream_function(lambda n: -1, True, 0) - sage: g = Stream_neg(f) + sage: g = Stream_neg(f, True) """ - super().__init__(series, series._is_sparse) + super().__init__(series, is_sparse) self._true_order = self._series._true_order @lazy_attribute @@ -2483,13 +2470,13 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_neg sage: f = Stream_function(lambda n: Zmod(6)(n), True, 2) - sage: h = Stream_neg(f) + sage: h = Stream_neg(f, True) sage: h._approximate_order 2 sage: [h[i] for i in range(5)] [0, 0, 4, 3, 2] """ - # this is the true order + # this is the true order, if self._series._true_order return self._series._approximate_order def get_coefficient(self, n): @@ -2504,7 +2491,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_neg, Stream_function) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_neg(f) + sage: g = Stream_neg(f, True) sage: g.get_coefficient(5) -5 sage: [g.get_coefficient(i) for i in range(10)] @@ -2521,13 +2508,13 @@ def is_nonzero(self): sage: from sage.data_structures.stream import (Stream_neg, Stream_function) sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_neg(f) + sage: g = Stream_neg(f, True) sage: g.is_nonzero() False sage: from sage.data_structures.stream import Stream_cauchy_invert sage: fi = Stream_cauchy_invert(f) - sage: g = Stream_neg(fi) + sage: g = Stream_neg(fi, True) sage: g.is_nonzero() True """ @@ -2541,9 +2528,10 @@ class Stream_cauchy_invert(Stream_unary): INPUT: - ``series`` -- a :class:`Stream` - - ``approximate_order`` -- ``None``, or a lower bound on the - order of ``Stream_cauchy_invert(series)`` + order of the resulting stream + + Instances of this class are always dense. EXAMPLES:: @@ -2564,7 +2552,7 @@ def __init__(self, series, approximate_order=None): sage: f = Stream_exact([1, -1]) sage: g = Stream_cauchy_invert(f) """ - super().__init__(series, series._is_sparse) + super().__init__(series, False) if approximate_order is not None: self._approximate_order = approximate_order self._zero = ZZ.zero() @@ -2615,39 +2603,6 @@ def _ainv(self): except TypeError: return self._series[v].inverse_of_unit() - def get_coefficient(self, n): - """ - Return the ``n``-th coefficient of ``self``. - - INPUT: - - - ``n`` -- integer; the degree for the coefficient - - EXAMPLES:: - - sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_cauchy_invert(f) - sage: g.get_coefficient(5) - 0 - sage: [g.get_coefficient(i) for i in range(10)] - [-2, 1, 0, 0, 0, 0, 0, 0, 0, 0] - """ - if not self._series._true_order: - self._ainv # this computes the true order of ``self`` - v = self._approximate_order - if n < v: - return ZZ.zero() - if n == v: - return self._ainv - - c = self._zero - for k in range(v, n): - l = self[k] - if l: - c += l * self._series[n - v - k] - return -c * self._ainv - def iterate_coefficients(self): """ A generator for the coefficients of ``self``. @@ -2668,7 +2623,6 @@ def iterate_coefficients(self): # Note that the first entry of the cache will correspond to # z^v, when the stream corresponds to a Laurent series. - # TODO: don't we need to distinguish between sparse and dense here? while True: n += 1 c = self._zero @@ -2715,12 +2669,12 @@ class Stream_map_coefficients(Stream_inexact): sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) sage: f = Stream_function(lambda n: 1, True, 1) - sage: g = Stream_map_coefficients(f, lambda n: -n) + sage: g = Stream_map_coefficients(f, lambda n: -n, True) sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] """ - def __init__(self, series, function, approximate_order=None, true_order=False): + def __init__(self, series, function, is_sparse, approximate_order=None, true_order=False): """ Initialize ``self``. @@ -2728,12 +2682,12 @@ def __init__(self, series, function, approximate_order=None, true_order=False): sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) sage: f = Stream_function(lambda n: -1, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: n + 1) + sage: g = Stream_map_coefficients(f, lambda n: n + 1, True) sage: TestSuite(g).run(skip="_test_pickling") """ self._function = function self._series = series - super().__init__(series._is_sparse, true_order) + super().__init__(is_sparse, true_order) if approximate_order is not None: self._approximate_order = approximate_order @@ -2746,7 +2700,7 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_map_coefficients sage: f = Stream_function(lambda n: Zmod(6)(n), True, 2) - sage: h = Stream_map_coefficients(f, lambda c: 3*c) + sage: h = Stream_map_coefficients(f, lambda c: 3*c, True) sage: h._approximate_order 2 sage: [h[i] for i in range(5)] @@ -2767,7 +2721,7 @@ def get_coefficient(self, n): sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) sage: f = Stream_function(lambda n: n, True, -1) - sage: g = Stream_map_coefficients(f, lambda n: n^2 + 1) + sage: g = Stream_map_coefficients(f, lambda n: n^2 + 1, True) sage: g.get_coefficient(5) 26 sage: [g.get_coefficient(i) for i in range(-1, 10)] @@ -2775,7 +2729,7 @@ def get_coefficient(self, n): sage: R. = ZZ[] sage: f = Stream_function(lambda n: n, True, -1) - sage: g = Stream_map_coefficients(f, lambda n: R(n).degree() + 1) + sage: g = Stream_map_coefficients(f, lambda n: R(n).degree() + 1, True) sage: [g.get_coefficient(i) for i in range(-1, 3)] [1, 0, 1, 1] """ @@ -2792,7 +2746,7 @@ def __hash__(self): sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) sage: f = Stream_function(lambda n: -1, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: n + 1) + sage: g = Stream_map_coefficients(f, lambda n: n + 1, True) sage: hash(g) == hash(g) True """ @@ -2812,19 +2766,21 @@ def __eq__(self, other): sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) sage: f = Stream_function(lambda n: -1, True, 0) sage: def plus_one(n): return n + 1 - sage: g = Stream_map_coefficients(f, plus_one) + sage: g = Stream_map_coefficients(f, plus_one, True) sage: g == f False - sage: g == Stream_map_coefficients(f, lambda n: n + 1) + sage: g == Stream_map_coefficients(f, lambda n: n + 1, True) False """ return (isinstance(other, type(self)) and self._series == other._series and self._function == other._function) -class Stream_shift(Stream_inexact): +class Stream_shift(Stream): """ - Operator for shifting the stream. + Operator for shifting a nonzero, nonexact stream. + + Instances of this class share the cache with its input stream. INPUT: @@ -2838,14 +2794,14 @@ def __init__(self, series, shift): EXAMPLES:: sage: from sage.data_structures.stream import Stream_shift - sage: from sage.data_structures.stream import Stream_exact - sage: h = Stream_exact([1], constant=3) + sage: from sage.data_structures.stream import Stream_function + sage: h = Stream_function(lambda n: n, True, -5) sage: M = Stream_shift(h, 2) sage: TestSuite(M).run(skip="_test_pickling") """ self._series = series self._shift = shift - super().__init__(series._is_sparse, series._true_order) + super().__init__(series._true_order) @lazy_attribute def _approximate_order(self): @@ -2862,9 +2818,24 @@ def _approximate_order(self): sage: [h[i] for i in range(5)] [2, 3, 4, 5, 0] """ - # this is the true order + # this is the true order, if self._series._true_order return self._series._approximate_order + self._shift + def order(self): + r""" + Return the order of ``self``, which is the minimum index + ``n`` such that ``self[n]`` is nonzero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_shift + sage: s = Stream_shift(Stream_function(lambda n: n, True, 0), 2) + sage: s.order() + 3 + """ + return self._series.order() + self._shift + + def __getitem__(self, n): """ Return the ``n``-th coefficient of ``self``. @@ -2947,7 +2918,7 @@ class Stream_derivative(Stream_inexact): - ``series`` -- a :class:`Stream` - ``shift`` -- a positive integer """ - def __init__(self, series, shift): + def __init__(self, series, shift, is_sparse): """ Initialize ``self``. @@ -2955,12 +2926,12 @@ def __init__(self, series, shift): sage: from sage.data_structures.stream import Stream_exact, Stream_derivative sage: f = Stream_exact([1,2,3]) - sage: f2 = Stream_derivative(f, 2) + sage: f2 = Stream_derivative(f, 2, True) sage: TestSuite(f2).run() """ self._series = series self._shift = shift - super().__init__(series._is_sparse, False) + super().__init__(is_sparse, False) @lazy_attribute def _approximate_order(self): @@ -2971,7 +2942,7 @@ def _approximate_order(self): sage: from sage.data_structures.stream import Stream_function, Stream_derivative sage: f = Stream_function(lambda n: Zmod(6)(n), True, 2) - sage: h = Stream_derivative(f, 3) + sage: h = Stream_derivative(f, 3, True) sage: h._approximate_order 0 sage: [h[i] for i in range(5)] @@ -2993,14 +2964,14 @@ def __getitem__(self, n): sage: f = Stream_function(lambda n: 1/n if n else 0, True, -2) sage: [f[i] for i in range(-5, 3)] [0, 0, 0, -1/2, -1, 0, 1, 1/2] - sage: f2 = Stream_derivative(f, 2) + sage: f2 = Stream_derivative(f, 2, True) sage: [f2[i] for i in range(-5, 3)] [0, -3, -2, 0, 0, 1, 2, 3] sage: f = Stream_function(lambda n: 1/n, True, 2) sage: [f[i] for i in range(-1, 4)] [0, 0, 0, 1/2, 1/3] - sage: f2 = Stream_derivative(f, 3) + sage: f2 = Stream_derivative(f, 3, True) sage: [f2[i] for i in range(-1, 4)] [0, 2, 6, 12, 20] """ @@ -3016,8 +2987,8 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_function sage: from sage.data_structures.stream import Stream_derivative sage: a = Stream_function(lambda n: 2*n, False, 1) - sage: f = Stream_derivative(a, 1) - sage: g = Stream_derivative(a, 2) + sage: f = Stream_derivative(a, 1, True) + sage: g = Stream_derivative(a, 2, True) sage: hash(f) == hash(f) True sage: hash(f) == hash(g) @@ -3039,11 +3010,11 @@ def __eq__(self, other): sage: from sage.data_structures.stream import Stream_function sage: from sage.data_structures.stream import Stream_derivative sage: a = Stream_function(lambda n: 2*n, False, 1) - sage: f = Stream_derivative(a, 1) - sage: g = Stream_derivative(a, 2) + sage: f = Stream_derivative(a, 1, True) + sage: g = Stream_derivative(a, 2, True) sage: f == g False - sage: f == Stream_derivative(a, 1) + sage: f == Stream_derivative(a, 1, True) True """ return (isinstance(other, type(self)) and self._shift == other._shift @@ -3058,9 +3029,9 @@ def is_nonzero(self): sage: from sage.data_structures.stream import Stream_exact, Stream_derivative sage: f = Stream_exact([1,2]) - sage: Stream_derivative(f, 1).is_nonzero() + sage: Stream_derivative(f, 1, True).is_nonzero() True - sage: Stream_derivative(f, 2).is_nonzero() # it might be nice if this gave False + sage: Stream_derivative(f, 2, True).is_nonzero() # it might be nice if this gave False True """ return self._series.is_nonzero() diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index a4d8005167f..5b573ef5b19 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -578,7 +578,8 @@ def map_coefficients(self, f): degree=coeff_stream._degree, constant=P.base_ring()(c)) return P.element_class(P, coeff_stream) - coeff_stream = Stream_map_coefficients(self._coeff_stream, func) + coeff_stream = Stream_map_coefficients(self._coeff_stream, func, + P.is_sparse()) return P.element_class(P, coeff_stream) def truncate(self, d): @@ -1732,8 +1733,10 @@ def _acted_upon_(self, scalar, self_on_left): constant=c, degree=coeff_stream._degree)) if self_on_left or R.is_commutative(): - return P.element_class(P, Stream_lmul(coeff_stream, scalar)) - return P.element_class(P, Stream_rmul(coeff_stream, scalar)) + return P.element_class(P, Stream_lmul(coeff_stream, scalar, + P.is_sparse())) + return P.element_class(P, Stream_rmul(coeff_stream, scalar, + P.is_sparse())) def _neg_(self): """ @@ -1796,7 +1799,7 @@ def _neg_(self): # -(-f) = f if isinstance(coeff_stream, Stream_neg): return P.element_class(P, coeff_stream._series) - return P.element_class(P, Stream_neg(coeff_stream)) + return P.element_class(P, Stream_neg(coeff_stream, P.is_sparse())) # === special functions === @@ -3903,10 +3906,12 @@ def derivative(self, *args): constant=coeff_stream._constant) return P.element_class(P, coeff_stream) - coeff_stream = Stream_derivative(self._coeff_stream, order) + coeff_stream = Stream_derivative(self._coeff_stream, order, + P.is_sparse()) if vars: coeff_stream = Stream_map_coefficients(coeff_stream, - lambda c: c.derivative(vars)) + lambda c: c.derivative(vars), + P.is_sparse()) return P.element_class(P, coeff_stream) def approximate_series(self, prec, name=None): @@ -4679,7 +4684,8 @@ def derivative(self, *args): v = gen_vars + vars d = -len(gen_vars) coeff_stream = Stream_map_coefficients(coeff_stream, - lambda c: R(c).derivative(v)) + lambda c: R(c).derivative(v), + P.is_sparse()) coeff_stream = Stream_shift(coeff_stream, d) return P.element_class(P, coeff_stream) @@ -4700,10 +4706,12 @@ def derivative(self, *args): constant=coeff_stream._constant) return P.element_class(P, coeff_stream) - coeff_stream = Stream_derivative(self._coeff_stream, order) + coeff_stream = Stream_derivative(self._coeff_stream, order, + P.is_sparse()) if vars: coeff_stream = Stream_map_coefficients(coeff_stream, - lambda c: c.derivative(vars)) + lambda c: c.derivative(vars), + P.is_sparse()) return P.element_class(P, coeff_stream) def _format_series(self, formatter, format_strings=False): @@ -5508,7 +5516,8 @@ def derivative_with_respect_to_p1(self, n=1): raise ValueError("arity must be equal to 1") coeff_stream = Stream_map_coefficients(self._coeff_stream, - lambda c: c.derivative_with_respect_to_p1(n)) + lambda c: c.derivative_with_respect_to_p1(n), + P.is_sparse()) coeff_stream = Stream_shift(coeff_stream, -n) return P.element_class(P, coeff_stream) @@ -5643,8 +5652,8 @@ def functorial_composition(self, *args): p = R.realization_of().p() # TODO: does the following introduce a memory leak? - g = Stream_map_coefficients(g._coeff_stream, p) - f = Stream_map_coefficients(self._coeff_stream, p) + g = Stream_map_coefficients(g._coeff_stream, p, P.is_sparse()) + f = Stream_map_coefficients(self._coeff_stream, p, P.is_sparse()) def g_cycle_type(s, n): # the cycle type of G[sigma] of any permutation sigma @@ -5900,8 +5909,8 @@ def arithmetic_product(self, *args, check=True): p = R.realization_of().p() # TODO: does the following introduce a memory leak? - g = Stream_map_coefficients(g._coeff_stream, p) - f = Stream_map_coefficients(self._coeff_stream, p) + g = Stream_map_coefficients(g._coeff_stream, p, P.is_sparse()) + f = Stream_map_coefficients(self._coeff_stream, p, P.is_sparse()) def coefficient(n): if not n: @@ -6162,7 +6171,8 @@ def __invert__(self): ZeroDivisionError: rational division by zero """ P = self.parent() - return P.element_class(P, Stream_dirichlet_invert(self._coeff_stream)) + return P.element_class(P, Stream_dirichlet_invert(self._coeff_stream, + P.is_sparse())) def __call__(self, p, *, check=True): r""" diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index fb7b3177e17..3bf145e62e7 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -725,7 +725,7 @@ def zero(self): sage: L.zero() 0 """ - return self.element_class(self, Stream_zero(self._sparse)) + return self.element_class(self, Stream_zero()) def characteristic(self): """ @@ -1847,7 +1847,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No if x in R: if not x and not constant: - coeff_stream = Stream_zero(self._sparse) + coeff_stream = Stream_zero() else: if not x: coeff_stream = Stream_exact([], @@ -2314,7 +2314,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No pass if x in R: if not x: - coeff_stream = Stream_zero(self._sparse) + coeff_stream = Stream_zero() else: p_dict = {} if self._arity == 1: From 7043d1cf5d937c3f947634c088e8bdc2c13e4d9f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 11 Oct 2022 15:56:59 +0200 Subject: [PATCH 176/414] remove redundant __init__ methods, remove finished TODOs --- src/sage/data_structures/stream.py | 62 ++------------------------ src/sage/rings/lazy_series_ring.py | 71 +++++++++--------------------- 2 files changed, 25 insertions(+), 108 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index a8d9f8f05d9..92aa652403a 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -1390,19 +1390,6 @@ class Stream_add(Stream_binaryCommutative): sage: [u[i] for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ - def __init__(self, left, right, is_sparse): - """ - Initialize ``self``. - - TESTS:: - - sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 1, True, 0) - sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_add(f, g, True) - """ - super().__init__(left, right, is_sparse) - @lazy_attribute def _approximate_order(self): """ @@ -1461,19 +1448,6 @@ class Stream_sub(Stream_binary): sage: [u[i] for i in range(10)] [1, 0, -1, -2, -3, -4, -5, -6, -7, -8] """ - def __init__(self, left, right, is_sparse): - """ - initialize ``self``. - - TESTS:: - - sage: from sage.data_structures.stream import (Stream_function, Stream_sub) - sage: f = Stream_function(lambda n: 1, True, 0) - sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_sub(f, g, True) - """ - super().__init__(left, right, is_sparse) - @lazy_attribute def _approximate_order(self): """ @@ -1540,19 +1514,6 @@ class Stream_cauchy_mul(Stream_binary): sage: [u[i] for i in range(10)] [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] """ - def __init__(self, left, right, is_sparse): - """ - initialize ``self``. - - TESTS:: - - sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) - sage: f = Stream_function(lambda n: 1, True, 0) - sage: g = Stream_function(lambda n: n^2, True, 0) - sage: h = Stream_cauchy_mul(f, g, True) - """ - super().__init__(left, right, is_sparse) - @lazy_attribute def _approximate_order(self): """ @@ -1647,26 +1608,6 @@ class Stream_dirichlet_convolve(Stream_binary): sage: [u[i] for i in range(1, 10)] [1, 3, 4, 7, 6, 12, 8, 15, 13] """ - def __init__(self, left, right, is_sparse): - """ - Initialize ``self``. - - sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) - sage: f = Stream_function(lambda n: n, True, 1) - sage: g = Stream_exact([1], constant=0) - sage: h = Stream_dirichlet_convolve(f, g, True) - sage: h[1] - Traceback (most recent call last): - ... - ValueError: Dirichlet convolution is only defined for coefficient streams with minimal index of nonzero coefficient at least 1 - sage: h = Stream_dirichlet_convolve(g, f, True) - sage: h[1] - Traceback (most recent call last): - ... - ValueError: Dirichlet convolution is only defined for coefficient streams with minimal index of nonzero coefficient at least 1 - """ - super().__init__(left, right, is_sparse) - @lazy_attribute def _approximate_order(self): """ @@ -2448,6 +2389,9 @@ class Stream_neg(Stream_unary): sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] """ + # TODO: maybe we should just inherit from `Stream` instead of + # inheriting from `Stream_unary` and do not create a copy of the + # cache def __init__(self, series, is_sparse): """ Initialize ``self``. diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 3bf145e62e7..27e0f963625 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -426,11 +426,6 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No sage: L(filter(is_odd, NN), -3) z^-3 + 3*z^-2 + 5*z^-1 + 7 + 9*z + 11*z^2 + 13*z^3 + O(z^4) - - .. TODO:: - - Add a method to change the sparse/dense implementation. - """ if valuation is not None and valuation not in ZZ: raise ValueError("the valuation must be an integer") @@ -482,10 +477,6 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No # Handle when it is a lazy series if isinstance(x, self.Element): - # if x._coeff_stream._is_sparse is not self._sparse: - # TODO: Implement a way to make a self._sparse copy - # raise NotImplementedError("cannot convert between sparse and dense") - # If x is known to be 0 if isinstance(x._coeff_stream, Stream_zero): if not constant: @@ -1769,10 +1760,6 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No sage: g = L([1,3,5,7,9], 5, -1); g z^5 + 3*z^6 + 5*z^7 + 7*z^8 + 9*z^9 - z^10 - z^11 - z^12 + O(z^13) - .. TODO:: - - Add a method to change the sparse/dense implementation. - Finally, ``x`` can be a polynomial:: sage: P. = QQ[] @@ -1875,30 +1862,27 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No return self.element_class(self, coeff_stream) if isinstance(x, LazyPowerSeries): - # if x._coeff_stream._is_sparse is self._sparse: - stream = x._coeff_stream - if isinstance(stream, Stream_exact): - if self._arity == 1: - BR = self.base_ring() - else: - BR = self._laurent_poly_ring - coeffs = [BR(val) for val in stream._initial_coefficients] - valuation = stream._approximate_order - for i, c in enumerate(coeffs): - if c: - valuation += i - coeffs = coeffs[i:] - break - else: - valuation += len(coeffs) - coeffs = [] - return self(coeffs, - degree=stream._degree, - constant=self.base_ring()(stream._constant), - valuation=valuation) - return self.element_class(self, stream) - # TODO: Implement a way to make a self._sparse copy - # raise NotImplementedError("cannot convert between sparse and dense") + stream = x._coeff_stream + if isinstance(stream, Stream_exact): + if self._arity == 1: + BR = self.base_ring() + else: + BR = self._laurent_poly_ring + coeffs = [BR(val) for val in stream._initial_coefficients] + valuation = stream._approximate_order + for i, c in enumerate(coeffs): + if c: + valuation += i + coeffs = coeffs[i:] + break + else: + valuation += len(coeffs) + coeffs = [] + return self(coeffs, + degree=stream._degree, + constant=self.base_ring()(stream._constant), + valuation=valuation) + return self.element_class(self, stream) if callable(x) or isinstance(x, (GeneratorType, map, filter)): if valuation is None: @@ -2259,10 +2243,6 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No sage: f = L([m[1],m[2],m[3]], valuation=1); f m[1] + m[2] + m[3] - .. TODO:: - - Add a method to change the sparse/dense implementation. - Finally, ``x`` can be a symmetric function:: sage: m = SymmetricFunctions(ZZ).m() @@ -2340,10 +2320,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No return self.element_class(self, coeff_stream) if isinstance(x, self.Element): - # if x._coeff_stream._is_sparse is self._sparse: - return self.element_class(self, x._coeff_stream) - # TODO: Implement a way to make a self._sparse copy - # raise NotImplementedError("cannot convert between sparse and dense") + return self.element_class(self, x._coeff_stream) if self._arity == 1: def check_homogeneous_of_degree(f, d): @@ -2712,10 +2689,6 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No sage: D = LazyDirichletSeriesRing(QQ, 't') sage: D(m) -1/(2^t) - 1/(3^t) - 1/(5^t) + 1/(6^t) - 1/(7^t) + O(1/(9^t)) - - .. TODO:: - - Add a method to make a copy of ``self._sparse``. """ if isinstance(x, (list, tuple)): p = self._internal_poly_ring(x) From d5d32de70c8f8a2566f46cc837007cd7dfd9ff03 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 1 Aug 2022 15:12:30 -0500 Subject: [PATCH 177/414] Initial commit --- src/sage/combinat/diagram.py | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/sage/combinat/diagram.py diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py new file mode 100644 index 00000000000..3ebe236b9a9 --- /dev/null +++ b/src/sage/combinat/diagram.py @@ -0,0 +1,66 @@ +r""" +Combinatorial diagrams + +A combinatorial diagram is a collection of cells `(i,j)` indexed by pairs of +natural numbers. The positions are indexed by rows and columns. For example, +a Ferrer's diagram is a diagram obtained from a partition +`\lambda = (\lambda_0, \lambda_1, \ldots, \lambda_\ell)` where the cells are +in rows `i` for `0 \leq i \leq \ell` and the cells in row `i` consist of +`(i,j)` for `0 \leq j < \lambda_i`. In English notation, the indices are read +from top left to bottom right as in a matrix. + +Indexing conventions are the same as +:class:`~sage.combinat.partition.Partition`. + +EXAMPLES: + + +Diagrams can be created by: + - explictly passing an iterable of all the cells + - providing a :class:`~sage.combinat.partition.Partition`, + in which case the diagram is the corresponding (English notation) Ferrers + diagram (see also :meth:`~sage.combination.partition.Partition.ferrers_diagram`) + - providing a :class:`~sage.combinat.permutation.Permutation`, in which + case the diagram is the corresponding Rothe diagram. + - providing a list of *death rays* which are cells `(i_0,j_0)` not present in the + diagram and have the property that there are no cells in the diagram that + have the form `(i,j_0)` with `i > i_0` and no cells in the diagram that + have the form `(i_0, j)` with `j > j_0`. The death rays kill all of the + cells to right and below of them. + + +Passing a list of all cells:: + + sage: from sage.combinat.diagram import Diagram, Diagrams + sage: cells = [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] + sage: D = Diagram(cells); D + [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] + sage: pp(D) + + +AUTHORS: + +- Trevor K. Karn (2022-08-01): initial version +""" + +# **************************************************************************** +# Copyright (C) 2013 Trevor K. Karn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +class Diagram(CombinatorialElement): + def __init__(self, parent, cells): + self._cells = cells + +class Diagrams(UniqueRepresentation, Parent): + def __init__(self): + pass + +class Diagrams_all(Diagrams): + def __init__(self): + pass From 9830fa34ea9334a6c91b7493cf2256f765a7165a Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 2 Aug 2022 09:16:17 -0500 Subject: [PATCH 178/414] Implement parent framework --- src/sage/combinat/diagram.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 3ebe236b9a9..78c7471c452 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -31,11 +31,10 @@ Passing a list of all cells:: - sage: from sage.combinat.diagram import Diagram, Diagrams + sage: from sage.combinat.diagram import Diagram sage: cells = [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] sage: D = Diagram(cells); D [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] - sage: pp(D) AUTHORS: @@ -53,14 +52,23 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -class Diagram(CombinatorialElement): - def __init__(self, parent, cells): +from sage.categories.sets_cat import Sets +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.list_clone import ClonableArray +from sage.structure.parent import Parent + +class Diagram(ClonableArray): + def __init__(self, cells): self._cells = cells class Diagrams(UniqueRepresentation, Parent): - def __init__(self): - pass -class Diagrams_all(Diagrams): def __init__(self): - pass + + Parent.__init__(self) + + def _element_constructor_(self, cells) + + return self.element_class(self, cells) + + Element = Diagram \ No newline at end of file From 4e22f9838f6aacb99f3d06c59b21f6ea6ba2a803 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 2 Aug 2022 12:36:18 -0500 Subject: [PATCH 179/414] Add parent/element framework of northwest diagrams --- src/sage/combinat/diagram.py | 393 ++++++++++++++++++++++++++++++++--- 1 file changed, 369 insertions(+), 24 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 78c7471c452..cef520e0e73 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -14,28 +14,26 @@ EXAMPLES: - -Diagrams can be created by: - - explictly passing an iterable of all the cells - - providing a :class:`~sage.combinat.partition.Partition`, - in which case the diagram is the corresponding (English notation) Ferrers - diagram (see also :meth:`~sage.combination.partition.Partition.ferrers_diagram`) - - providing a :class:`~sage.combinat.permutation.Permutation`, in which - case the diagram is the corresponding Rothe diagram. - - providing a list of *death rays* which are cells `(i_0,j_0)` not present in the - diagram and have the property that there are no cells in the diagram that - have the form `(i,j_0)` with `i > i_0` and no cells in the diagram that - have the form `(i_0, j)` with `j > j_0`. The death rays kill all of the - cells to right and below of them. - - -Passing a list of all cells:: +To create an arbirtrary diagram, pass a list of all cells:: sage: from sage.combinat.diagram import Diagram sage: cells = [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] sage: D = Diagram(cells); D - [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] - + [(0, 0), (0, 1), (1, 0), (1, 1), (4, 4), (4, 5), (4, 6), (5, 4), (7, 6)] + +We can visualize the diagram by printing ``O``'s and ``.``'s. ``O``'s are +present in the cells which are present in the diagram and a ``.`` represents +the absence of a cell in the diagram:: + + sage: D.pp() + O O . . . . . + O O . . . . . + . . . . . . . + . . . . . . . + . . . . O O O + . . . . O . . + . . . . . . . + . . . . . . O AUTHORS: @@ -52,23 +50,370 @@ # https://www.gnu.org/licenses/ # **************************************************************************** + from sage.categories.sets_cat import Sets +from sage.sets.non_negative_integers import NonNegativeIntegers as NN +from sage.combinat.partition import Partition +from sage.combinat.permutation import Permutation from sage.structure.unique_representation import UniqueRepresentation from sage.structure.list_clone import ClonableArray from sage.structure.parent import Parent class Diagram(ClonableArray): - def __init__(self, cells): - self._cells = cells + r""" + EXAMPLES:: + + sage: + """ + @staticmethod + def __classcall_private__(self, cells, **kwargs): + r""" + Normalize the input so that it lives in the correct parent. + + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) + sage: D.parent() + Combinatorial diagrams + """ + return Diagrams()(cells, **kwargs) + + def __init__(self, cells, **kwargs): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: D1.cells() + [(0, 2), (0, 3), (1, 1), (3, 2)] + sage: D1.n_rows() + 4 + sage: D1.n_cols() + 4 + + We can specify the number of rows and columns explicitly, + in case they are supposed to be empty:: + + sage: D2 = Diagram([(0,2),(0,3),(1,1),(3,2)], n_cols=5) + sage: D2.cells() + [(0, 2), (0, 3), (1, 1), (3, 2)] + sage: D2.n_cols() + 5 + sage: D2.pp() + . . O O . + . O . . . + . . . . . + . . O . . + """ + self._cells = {c: True for c in cells} + self._n_rows = kwargs.pop('n_rows', max(c[0] for c in self._cells) + 1) + self._n_cols = kwargs.pop('n_cols', max(c[1] for c in self._cells) + 1) + + ClonableArray.__init__(self, Diagrams(), cells, check=False) + + def _hash_(self): + r""" + TESTS:: + + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: hash(D) + 5125392318921082108 + """ + return hash(tuple(sorted(self._cells))) + + def __contains__(self, other): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: (1, 2) in D + False + sage: (0, 2) in D + True + sage: (2, 1) in D + False + sage: (3, 2) in D + True + """ + return other in self._cells + + def __repr__(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]); D + [(0, 2), (0, 3), (1, 1), (3, 2)] + """ + return str(list(self._cells)) + + def pp(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: Diagram([(0,0), (0,3), (2,2), (2,4)]).pp() + O . . O . + . . . . . + . . O . O + """ + output_str = '' + + for i in range(self._n_rows): + for j in range(self._n_cols): + if (i, j) in self: + output_str += 'O ' + else: + output_str += '. ' + output_str += '\n' + + print(output_str) + + def n_rows(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: D1.n_rows() + 4 + """ + + return self._n_rows + + def n_cols(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: D1.n_cols() + 4 + """ + return self._n_cols + + def cells(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: D1.cells() + [(0, 2), (0, 3), (1, 1), (3, 2)] + """ + return list(self._cells.keys()) + + def n_cells(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: D1.n_cells() + 4 + """ + return len(self._cells) + + def check(self): + r""" + Check that this is a valid diagram by checking that it is an iterable + of length-two tuples of integers. + + .. WARNING:: + + This method is required for ``Diagram`` to be a subclass of + :class:`~sage.structure.list_clone.ClonableArray`, however the check + is *not* automatically performed upon creation of the element. + + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) + sage: D.check() + sage: notD = Diagram([(0,1,2), (0,1), (2,2)]) + sage: notD.check() + Traceback (most recent call last): + ... + AssertionError + """ + assert all(isinstance(c, tuple) and len(c) == 2 for c in self._cells) class Diagrams(UniqueRepresentation, Parent): + r""" + The class of combinatorial diagrams. + A *combinatorial diagram* is a set of cells indexed by pairs of natural + numbers. + """ def __init__(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagrams + sage: Dgms = Diagrams(); Dgms + Combinatorial diagrams + + TESTS:: + + sage: TestSuite(Dgms).run() + """ + + Parent.__init__(self, category=Sets()) + + def __repr__(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagrams + sage: Dgms = Diagrams(); Dgms + Combinatorial diagrams + """ + return 'Combinatorial diagrams' + + def _element_constructor_(self, cells, **kwargs): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagrams + sage: Dgms = Diagrams() + sage: Dgms([(0,1),(2,2)]).pp() + . O . + . . . + . . O + + TESTS:: + + sage: TestSuite(Dgms).run() + """ + return self.element_class(cells, **kwargs) + + def _an_element_(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagrams + sage: Dgms = Diagrams() + sage: D = Dgms.an_element(); D + [(0, 2), (1, 1), (2, 3)] + sage: D.pp() + . . O . + . O . . + . . . O + """ + return self([(0, 2), (1, 1), (2, 3)]) + + Element = Diagram + +#################### +# Northwest diagrams +#################### + +class NorthwestDiagram(Diagram): + r""" + A diagram is a set of cells indexed by natural numbers. Such a diagram + has the *northwest property* if the presence of cells `(i1, j1)` and + `(i2, j2)` implies the presence of the cell + `(\min(i1, i2), \min(j1, j2))`. Diagrams with the northwest property are + called *northwest diagrams*. + + For general diagrams see :class:`Diagram`. + """ + @staticmethod + def __classcall_private__(self, cells, **kwargs): + r""" + """ + return NorthwestDiagrams()(cells, **kwargs) + + def check(self): + r""" + A diagram has the northwest property if the presence of cells + `(i1, j1)` and `(i2, j2)` implies the presence of the cell + `(min(i1, i2), min(j1, j2))` + + .. WARNING:: + + This method is required for `Diagram` to be a subclass of + :class:`~sage.structure.list_clone.ClonableArray`, however the check + is *not* automatically performed upon creation of the element. + """ + from itertools import combinations + assert all((min(i1,i2), min(j1,j2)) in self + for (i1, j1), (i2, j2) in combinations(self._cells, 2)) + +class NorthwestDiagrams(Diagrams): + r""" + The class of northwest diagrams. + + A diagram has the *northwest property* if the presence of cells + `(i1, j1)` and `(i2, j2)` implies the presence of the cell + `(min(i1, i2), min(j1, j2))`. For the class of general diagrams, see + :class:`Diagrams`. + + One may create an instance of a northwest diagram either by directly + calling :class:`NorthwestDiagram` or by creating an instance of the + parent class `NorthwestDiagrams` (with an `s`) and calling it on a `list` + of tuples consisting of the coordinates of the diagram. + + EXAMPLES:: + + Additionally, there are natural constructions of a northwest diagram + given the data of a permutation (Rothe diagrams are the protypical example + of northwest diagrams), or the data of a partition of an integer, or a + skew partition. + + The Rothe diagram `D(\omega)` of a permutation `\omega` is specified by + the cells + + .. MATH:: + + \{ (i, j) : i < \omega^{-1}(j) \text{ and } j < \omega(i)} + + We can construct one by calling :meth:`rothe_diagram` method on the parent + class :class:`NorthwestDiagrams`. + + EXAMPLES:: + + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: NWDgms = NorthwestDiagrams + + To turn a Ferrers diagram into a northwest diagram, we may call the + :meth:`ferrers_diagram` method. This will return a Ferrer's diagram in the + parent of all northwest diagrams. For many use-cases it is probably better + to get Ferrer's diagrams by the corresponding method on partitons, namely + :meth:`sage.combinat.partitions.Partitions.ferrers_diagram`. + + It is also possible to turn a Ferrers diagram of a skew partition into a + northwest diagram, altough it is more subtle than just using the skew + diagram itself. + """ + + def __repr__(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: NWDgms = NorthwestDiagrams(); NWDgms + Combinatorial northwest diagrams + """ + return 'Combinatorial northwest diagrams' - Parent.__init__(self) + def _an_element_(self): + r""" + EXAMPLES:: - def _element_constructor_(self, cells) + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: NWDgms = NorthwestDiagrams() + sage: NWD = NWDgms.an_element(); NWD + [(0, 1), (0, 2), (1, 1), (2, 3)] + sage: NWD.pp() + . O O . + . O . . + . . . O + """ + return self([(0, 1), (0, 2), (1, 1), (2, 3)]) - return self.element_class(self, cells) - Element = Diagram \ No newline at end of file + Element = NorthwestDiagram \ No newline at end of file From 9946e835c8726d5108e4cafa61d38d1479990650 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 3 Aug 2022 12:24:38 -0500 Subject: [PATCH 180/414] Add outside_corners to SkewPartition --- src/sage/combinat/skew_partition.py | 36 ++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/skew_partition.py b/src/sage/combinat/skew_partition.py index ea6b3b52057..dcb376b0a90 100644 --- a/src/sage/combinat/skew_partition.py +++ b/src/sage/combinat/skew_partition.py @@ -126,6 +126,7 @@ - Mike Hansen: Initial version - Travis Scrimshaw (2013-02-11): Factored out ``CombinatorialClass`` +- Trevor K. Karn (2022-08-03): Add ``outside_corners`` """ #***************************************************************************** # Copyright (C) 2007 Mike Hansen , @@ -739,7 +740,14 @@ def conjugate(self): def outer_corners(self): """ - Return a list of the outer corners of ``self``. + Return a list of the outer corners of ``self``. These are corners + which are contained inside of the shape. For the corners which are + outside of the shape, use :meth:`outside_corners`. + + .. SEEALSO:: + + - :meth:`sage.combinat.skew_partition.SkewPartition.outside_corners` + - :meth:`sage.combinat.partition.Partition.outside_corners` EXAMPLES:: @@ -1209,6 +1217,32 @@ def jacobi_trudi(self): m.append(row) return H(m) + def outside_corners(self): + r""" + Return the outside corners of ``self``. + + The outside corners are corners which are outside of the shape. This + should not be confused with :meth:`outer_corners` which consists of + corners inside the shape. It returns a result analogous to the + ``.outside_corners()`` method on (non-skew) ``Partitions``. + + .. SEEALSO:: + + - :meth:`sage.combinat.skew_partition.SkewPartition.outer_corners` + - :meth:`sage.combinat.partition.Partition.outside_corners` + + EXAMPLES:: + + sage: mu = SkewPartition([[3,2,1],[2,1]]) + sage: mu.pp() + * + * + * + sage: mu.outside_corners() + [(0, 3), (1, 2), (2, 1), (3, 0)] + """ + return self.outer().outside_corners() + def row_lengths_aux(skp): """ EXAMPLES:: From 40656a13e1293dac4d1c11a218cabefbf86f4251 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 3 Aug 2022 20:04:52 -0500 Subject: [PATCH 181/414] Clarify some of the language around corners' --- src/sage/combinat/partition.py | 8 ++++++++ src/sage/combinat/skew_partition.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index f6cf8e4f364..b1f9810beaf 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -3957,6 +3957,10 @@ def corners(self): where `i` and `j` are the coordinates of the respective corner. The coordinates are counted from `0`. + .. NOTE:: + + This is referred to as an "inner corner" in [Sag2001]_. + EXAMPLES:: sage: Partition([3,2,1]).corners() @@ -4029,6 +4033,10 @@ def outside_corners(self): where `i` and `j` are the coordinates of the respective corner. The coordinates are counted from `0`. + .. NOTE:: + + These are called "outer corners" in [Sag2001]_. + EXAMPLES:: sage: Partition([2,2,1]).outside_corners() diff --git a/src/sage/combinat/skew_partition.py b/src/sage/combinat/skew_partition.py index dcb376b0a90..3f60e008c2e 100644 --- a/src/sage/combinat/skew_partition.py +++ b/src/sage/combinat/skew_partition.py @@ -744,6 +744,13 @@ def outer_corners(self): which are contained inside of the shape. For the corners which are outside of the shape, use :meth:`outside_corners`. + .. WARNING:: + + In the case that `self` is an honest (rather than skew) partition, + these are the :meth:`~sage.combinat.partition.Partition.corners` + of the outer partition. In the language of [Sag2001]_ these would + be the "inner corners" of the outer partition. + .. SEEALSO:: - :meth:`sage.combinat.skew_partition.SkewPartition.outside_corners` From 3e994923efbf2934d5657b44381e5e055e7986ca Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 3 Aug 2022 20:06:16 -0500 Subject: [PATCH 182/414] Add backward_slide (and alias reverse_slide) --- src/sage/combinat/skew_tableau.py | 122 ++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 32b116784dd..9d45cdc69f4 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -907,6 +907,128 @@ def slide(self, corner=None, return_vacated=False): return (SkewTableau(new_st), (spotl, spotc)) return SkewTableau(new_st) + def backward_slide(self, corner=None): + r""" + Apply a backward jeu de taquin slide on the specified outside corner. + + Backward jeu de taquin slides are defined in section 3.7 of + [Sag2001]_. + + .. WARNING:: + + The :meth:`inner_corners` and :meth:`outer_corners` are the + :meth:`sage.combinat.partition.Partition.corners` of the inner and + outer partitions of the skew shape. They are different from the + inner/outer corners defined in [Sag2001]_. + + The "inner corners" of [Sag2001]_ may be found by calling + :meth:`outer_corners`. The "outer corners" of [Sag2001]_ may be + found by calling ``self.outer_shape().outside_corners()``. + + EXAMPLES:: + + sage: T = SkewTableaux()([[2, 2], [4, 4], [5]]) + sage: Tableaux.options.display='array' + sage: Q = T.backward_slide(); Q + . 2 2 + 4 4 + 5 + sage: Q.backward_slide((1, 2)) + . 2 2 + . 4 4 + 5 + sage: Q.reverse_slide((1, 2)) == Q.backward_slide((1, 2)) + True + + TESTS:: + + sage: T = SkewTableaux()([[2, 2], [4, 4], [5]]) + sage: Q = T.backward_slide((0, 2)) + sage: Q.backward_slide((2,1)) + . 2 2 + . 4 + 4 5 + sage: Q.backward_slide((3,0)) + . 2 2 + . 4 + 4 + 5 + sage: Q = T.backward_slide((2,1)); Q + . 2 + 2 4 + 4 5 + sage: Q.backward_slide((3,0)) + . 2 + . 4 + 2 5 + 4 + sage: Q = T.backward_slide((3,0)); Q + . 2 + 2 4 + 4 + 5 + sage: Q.backward_slide((4,0)) + . 2 + . 4 + 2 + 4 + 5 + sage: Tableaux.options.display='list' + """ + new_st = self.to_list() + inner_outside_corners = self.inner_shape().outside_corners() + outer_outisde_corners = self.outer_shape().outside_corners() + if corner is not None: + if tuple(corner) not in outer_outisde_corners: + raise ValueError("corner must be an outside corner \ + of the outer shape") + else: + if not outer_outisde_corners: + return self + else: + corner = outer_outisde_corners[0] + + i, j = corner + + # add the empty cell + # the column only matters if it is zeroth column, in which + # case we need to add a new row. + if not j: + new_st.append(list()) + new_st[i].append(None) + + while (i, j) not in inner_outside_corners: + # get the value of the cell above the temporarily empty cell (if + # it exists) + if i > 0: + P_up = new_st[i-1][j] + else: + P_up = None + + # get the value of the cell to the left of the temp. empty cell + # (if it exists) + if j > 0: + P_left = new_st[i][j-1] + else: + P_left = None + + # get the next cell + if P_left > P_up: + new_st[i][j] = P_left + i, j = (i, j-1) + else: # if they are equal, we slide up + new_st[i][j] = P_up + i, j = (i-1, j) + + # We don't need to reset the intermediate cells inside the loop + # because the conditional above will continue to overwrite it until + # the while loop terminates. We do need to reset it at the end. + new_st[i][j] = None + + return SkewTableau(new_st) + + reverse_slide = backward_slide + def rectify(self, algorithm=None): """ Return a :class:`StandardTableau`, :class:`SemistandardTableau`, From 19380a6497053a863b6843db7b7bf458b67aa12e Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 3 Aug 2022 22:23:24 -0500 Subject: [PATCH 183/414] First draft of peelables algorithm --- src/sage/combinat/diagram.py | 64 +++++++++++++++++++++++++++++++ src/sage/combinat/skew_tableau.py | 19 +++++---- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index cef520e0e73..0a0341005f1 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -58,6 +58,8 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.list_clone import ClonableArray from sage.structure.parent import Parent +from sage.combinat.tableau import Tableau +from sage.combinat.skew_tableau import SkewTableaux class Diagram(ClonableArray): r""" @@ -109,6 +111,8 @@ def __init__(self, cells, **kwargs): self._cells = {c: True for c in cells} self._n_rows = kwargs.pop('n_rows', max(c[0] for c in self._cells) + 1) self._n_cols = kwargs.pop('n_cols', max(c[1] for c in self._cells) + 1) + self._n_nonempty_rows = len(set(i for i in self._cells)) + self._n_nonempty_cols = len(set(j for j in self._cells)) ClonableArray.__init__(self, Diagrams(), cells, check=False) @@ -343,6 +347,64 @@ def check(self): assert all((min(i1,i2), min(j1,j2)) in self for (i1, j1), (i2, j2) in combinations(self._cells, 2)) + def peelable_tableaux(self): + r""" + Given a northwest diagram `D`, a tableau `T` is said to be + `D`-peelable if... + + EXAMPLES: + + If the diagram is only one column, there is only one peelable tableau + + sage: from sage.combinat.diagram import NorthwestDiagram + sage: NWD = NorthwestDiagram([(0,0), (2,0)]) + sage: NWD.peelable_tableaux() + {[[0], [2]]} + + .. ALGORITHM:: + + This implementation uses the algorithm suggested in remark 25 + of [...]_. + """ + # if there is a single column in the diagram then there is only + # one posslbe peelable tableau. + if self._n_nonempty_cols == 1: + return {Tableau([[i+1] for i,j in self._cells])} + + first_col = min(j for i,j in self._cells) + + # get the diagram without the first column + Dhat = NorthwestDiagram([c for c in self._cells if c[1] != first_col]) + + k = self.n_cells() - Dhat.n_cells() + + peelables = set() + + for Q in Dhat.peelable_tableaux(): + print(Q) + # get the vertical strips + mu = Q.shape() + vertical_strips = mu.add_vertical_border_strip(k) + for s in vertical_strips: + sQ = SkewTableaux()(Q) # sQ is skew - get it? + new_cells = (s/mu).cells() + + # perform the jeu de taquin slides + for c in new_cells: + sQ = sQ.backward_slide(c) + print(sQ) + + # create the new tableau by filling the columns + sQ_new = sQ.to_list() + for n, (i, j) in enumerate(sQ.cells_containing(None)): + sQ_new[i][j] = n + 1 + + T = Tableau(sQ_new) + if T.is_column_strict(): + peelables.add(T) + + return peelables + class NorthwestDiagrams(Diagrams): r""" The class of northwest diagrams. @@ -412,6 +474,8 @@ def _an_element_(self): . O O . . O . . . . . O + sage: NWD.parent() is NWDgms + True """ return self([(0, 1), (0, 2), (1, 1), (2, 3)]) diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 9d45cdc69f4..8c7ee144fcc 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -1013,13 +1013,18 @@ def backward_slide(self, corner=None): P_left = None # get the next cell - if P_left > P_up: - new_st[i][j] = P_left - i, j = (i, j-1) - else: # if they are equal, we slide up - new_st[i][j] = P_up - i, j = (i-1, j) - + try: + if P_left > P_up: + new_st[i][j] = P_left + i, j = (i, j-1) + else: # if they are equal, we slide up + new_st[i][j] = P_up + i, j = (i-1, j) + except TypeError: + # if the addition of the original corner + # is automatically a skew partition, just + # return it. + break # We don't need to reset the intermediate cells inside the loop # because the conditional above will continue to overwrite it until # the while loop terminates. We do need to reset it at the end. From 513948fe63abe4c63c7dd9d80328cb1b4618f24a Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Thu, 4 Aug 2022 17:29:22 -0500 Subject: [PATCH 184/414] Peelable tableaux pass all tests --- src/sage/combinat/diagram.py | 157 +++++++++++++++++++++++++----- src/sage/combinat/skew_tableau.py | 10 ++ 2 files changed, 142 insertions(+), 25 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 0a0341005f1..3165d13112f 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -3,7 +3,7 @@ A combinatorial diagram is a collection of cells `(i,j)` indexed by pairs of natural numbers. The positions are indexed by rows and columns. For example, -a Ferrer's diagram is a diagram obtained from a partition +a Ferrer's diagram is a diagram obtained from a partition `\lambda = (\lambda_0, \lambda_1, \ldots, \lambda_\ell)` where the cells are in rows `i` for `0 \leq i \leq \ell` and the cells in row `i` consist of `(i,j)` for `0 \leq j < \lambda_i`. In English notation, the indices are read @@ -21,7 +21,7 @@ sage: D = Diagram(cells); D [(0, 0), (0, 1), (1, 0), (1, 1), (4, 4), (4, 5), (4, 6), (5, 4), (7, 6)] -We can visualize the diagram by printing ``O``'s and ``.``'s. ``O``'s are +We can visualize the diagram by printing ``O``'s and ``.``'s. ``O``'s are present in the cells which are present in the diagram and a ``.`` represents the absence of a cell in the diagram:: @@ -34,7 +34,7 @@ . . . . O . . . . . . . . . . . . . . . O - + AUTHORS: - Trevor K. Karn (2022-08-01): initial version @@ -111,8 +111,8 @@ def __init__(self, cells, **kwargs): self._cells = {c: True for c in cells} self._n_rows = kwargs.pop('n_rows', max(c[0] for c in self._cells) + 1) self._n_cols = kwargs.pop('n_cols', max(c[1] for c in self._cells) + 1) - self._n_nonempty_rows = len(set(i for i in self._cells)) - self._n_nonempty_cols = len(set(j for j in self._cells)) + self._n_nonempty_rows = len(set(i for i,j in self._cells)) + self._n_nonempty_cols = len(set(j for i,j in self._cells)) ClonableArray.__init__(self, Diagrams(), cells, check=False) @@ -228,7 +228,7 @@ def check(self): .. WARNING:: - This method is required for ``Diagram`` to be a subclass of + This method is required for ``Diagram`` to be a subclass of :class:`~sage.structure.list_clone.ClonableArray`, however the check is *not* automatically performed upon creation of the element. @@ -237,11 +237,6 @@ def check(self): sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) sage: D.check() - sage: notD = Diagram([(0,1,2), (0,1), (2,2)]) - sage: notD.check() - Traceback (most recent call last): - ... - AssertionError """ assert all(isinstance(c, tuple) and len(c) == 2 for c in self._cells) @@ -250,7 +245,7 @@ class Diagrams(UniqueRepresentation, Parent): The class of combinatorial diagrams. A *combinatorial diagram* is a set of cells indexed by pairs of natural - numbers. + numbers. """ def __init__(self): r""" @@ -319,7 +314,7 @@ class NorthwestDiagram(Diagram): r""" A diagram is a set of cells indexed by natural numbers. Such a diagram has the *northwest property* if the presence of cells `(i1, j1)` and - `(i2, j2)` implies the presence of the cell + `(i2, j2)` implies the presence of the cell `(\min(i1, i2), \min(j1, j2))`. Diagrams with the northwest property are called *northwest diagrams*. @@ -329,8 +324,14 @@ class NorthwestDiagram(Diagram): def __classcall_private__(self, cells, **kwargs): r""" """ + # TODO: Assert that cells is sorted in lex order to speed up lookup. return NorthwestDiagrams()(cells, **kwargs) + def __init__(self, cells, **kwargs): + + Diagram.__init__(self, cells, **kwargs) + ClonableArray.__init__(self, NorthwestDiagrams(), cells, check=False) + def check(self): r""" A diagram has the northwest property if the presence of cells @@ -339,9 +340,9 @@ def check(self): .. WARNING:: - This method is required for `Diagram` to be a subclass of + This method is required for `Diagram` to be a subclass of :class:`~sage.structure.list_clone.ClonableArray`, however the check - is *not* automatically performed upon creation of the element. + is *not* automatically performed upon creation of the element. """ from itertools import combinations assert all((min(i1,i2), min(j1,j2)) in self @@ -354,18 +355,123 @@ def peelable_tableaux(self): EXAMPLES: - If the diagram is only one column, there is only one peelable tableau + If the diagram is only one column, there is only one peelable tableau:: sage: from sage.combinat.diagram import NorthwestDiagram sage: NWD = NorthwestDiagram([(0,0), (2,0)]) sage: NWD.peelable_tableaux() - {[[0], [2]]} + {[[1], [3]]} + + From [...]_ we know that there is only one peelable tableau for the + Rothe diagram of the permutation (in one line notation) `251643`:: + + sage: D = NorthwestDiagram([(1, 2), (1, 3), (3, 2), (3, 3), (4, 2)]) + sage: D.pp() + . . . . + . . O O + . . . . + . . O O + . . O . + + sage: D.peelable_tableaux() + {[[2, 2], [4, 4], [5]]} + + Here are all the intermediate steps to compute the peelables for the + Rothe diagram of (in one-line notation) `64817235`. They are listed from + deepest in the recursion to the final step. The recursion has depth five + in this case so we will label the intermediate tableaux by `D_i` where + `i` is the step in the recursion at which they appear. + + Start with the one that has a single column:: + + sage: D5 = NorthwestDiagram([(2,0)]); D5.pp() + . + . + O + sage: D5.peelable_tableaux() + {[[3]]} + + Now we know all of the `D_5` peelables, so we can compute the `D_4` + peelables. + + sage: D4 = NorthwestDiagram([(0, 0), (2,0), (4, 0), (2, 2)]) + sage: D4.pp() + O . . + . . . + O . O + . . . + O . . + + sage: D4.peelable_tableaux() + {[[1, 3], [3], [5]]} + + There is only one `D_4` peelable, so we can compute the `D_3` + peelables:: + + sage: D3 = NorthwestDiagram([(0,0), (0,1), (2, 1), (2, 3), (4,1)]) + sage: D3.pp() + O O . . + . . . . + . O . O + . . . . + . O . . + + sage: D3.peelable_tableaux() + {[[1, 1], [3, 3], [5]], [[1, 1, 3], [3], [5]]} + + Now compute the `D_2` peelables:: + + sage: cells = [(0,0), (0,1), (0,2), (1,0), (2,0), (2,2), (2,4), + ....: (4,0), (4,2)] + sage: D2 = NorthwestDiagram(cells); D2.pp() + O O O . . + O . . . . + O . O . O + . . . . . + O . O . . + + sage: D2.peelable_tableaux() + {[[1, 1, 1], [2, 3, 3], [3, 5], [5]], + [[1, 1, 1, 3], [2, 3], [3, 5], [5]]} + + And the `D_1` peelables:: + + sage: cells = [(0,0), (0,1), (0,2), (0,3), (1,0), (1,1), (2,0), + ....: (2,1), (2,3), (2,5), (4,0), (4,1), (4,3)] + sage: D1 = NorthwestDiagram(cells); D1.pp() + O O O O . . + O O . . . . + O O . O . O + . . . . . . + O O . O . . + + sage: D1.peelable_tableaux() + {[[1, 1, 1, 1], [2, 2, 3, 3], [3, 3, 5], [5, 5]], + [[1, 1, 1, 1, 3], [2, 2, 3], [3, 3, 5], [5, 5]]} + + Which we can use to get the `D` peelables:: + + sage: cells = [(0,0), (0,1), (0,2), (0,3), (0,4), + ....: (1,0), (1,1), (1,2), + ....: (2,0), (2,1), (2,2), (2,4), (2,6), + ....: (4,1), (4,2), (4,4)] + sage: D = NorthwestDiagram(cells); D.pp() + O O O O O . . + O O O . . . . + O O O . O . O + . . . . . . . + . O O . O . . + sage: D.peelable_tableaux() .. ALGORITHM:: This implementation uses the algorithm suggested in remark 25 of [...]_. """ + # TODO: There is a condition on the first column (if the rows in Dhat + # are a subset of the rows in the first column) which simplifies the + # description without performing JDT, so we should implement that + # if there is a single column in the diagram then there is only # one posslbe peelable tableau. if self._n_nonempty_cols == 1: @@ -373,31 +479,32 @@ def peelable_tableaux(self): first_col = min(j for i,j in self._cells) + # TODO: The next two lines of code could be optimized by only + # looping over self._cells once (rather than two separate times) # get the diagram without the first column Dhat = NorthwestDiagram([c for c in self._cells if c[1] != first_col]) + # get the values from the first column + new_vals = sorted(i + 1 for i, j in self._cells if j == first_col) + k = self.n_cells() - Dhat.n_cells() peelables = set() for Q in Dhat.peelable_tableaux(): - print(Q) # get the vertical strips mu = Q.shape() vertical_strips = mu.add_vertical_border_strip(k) for s in vertical_strips: sQ = SkewTableaux()(Q) # sQ is skew - get it? new_cells = (s/mu).cells() - # perform the jeu de taquin slides for c in new_cells: sQ = sQ.backward_slide(c) - print(sQ) - # create the new tableau by filling the columns sQ_new = sQ.to_list() - for n, (i, j) in enumerate(sQ.cells_containing(None)): - sQ_new[i][j] = n + 1 + for n in range(k): + sQ_new[n][0] = new_vals[n] T = Tableau(sQ_new) if T.is_column_strict(): @@ -449,7 +556,7 @@ class :class:`NorthwestDiagrams`. It is also possible to turn a Ferrers diagram of a skew partition into a northwest diagram, altough it is more subtle than just using the skew - diagram itself. + diagram itself. """ def __repr__(self): @@ -480,4 +587,4 @@ def _an_element_(self): return self([(0, 1), (0, 2), (1, 1), (2, 3)]) - Element = NorthwestDiagram \ No newline at end of file + Element = NorthwestDiagram diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 8c7ee144fcc..20add441c6b 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -7,6 +7,7 @@ - Mike Hansen: Initial version - Travis Scrimshaw, Arthur Lubovsky (2013-02-11): Factored out ``CombinatorialClass`` +- Trevor K. Karn (2022-08-03): added `backward_slide` """ # **************************************************************************** # Copyright (C) 2007 Mike Hansen , @@ -940,6 +941,15 @@ def backward_slide(self, corner=None): sage: Q.reverse_slide((1, 2)) == Q.backward_slide((1, 2)) True + sage: T = SkewTableaux()([[1, 3],[3],[5]]); T + 1 3 + 3 + 5 + sage: T.reverse_slide((1,1)) + . 1 + 3 3 + 5 + TESTS:: sage: T = SkewTableaux()([[2, 2], [4, 4], [5]]) From 676747900bd264f05300a42bb605ab423936b549 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 5 Aug 2022 08:06:36 -0500 Subject: [PATCH 185/414] Fix bug in JDT --- src/sage/combinat/skew_tableau.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 20add441c6b..0578404a643 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -1007,34 +1007,33 @@ def backward_slide(self, corner=None): new_st.append(list()) new_st[i].append(None) + + while (i, j) not in inner_outside_corners: # get the value of the cell above the temporarily empty cell (if # it exists) if i > 0: P_up = new_st[i-1][j] else: - P_up = None + P_up = -1 # a dummy value less than all positive numbers # get the value of the cell to the left of the temp. empty cell # (if it exists) if j > 0: P_left = new_st[i][j-1] else: - P_left = None + P_left = -1 # a dummy value less than all positive numbers # get the next cell - try: - if P_left > P_up: - new_st[i][j] = P_left - i, j = (i, j-1) - else: # if they are equal, we slide up - new_st[i][j] = P_up - i, j = (i-1, j) - except TypeError: - # if the addition of the original corner - # is automatically a skew partition, just - # return it. - break + # p_left will always be positive, but if P_left + # and P_up are both None, then it will return + # -1, which doesn't trigger the conditional + if P_left > P_up: + new_st[i][j] = P_left + i, j = (i, j - 1) + else: # if they are equal, we slide up + new_st[i][j] = P_up + i, j = (i - 1, j) # We don't need to reset the intermediate cells inside the loop # because the conditional above will continue to overwrite it until # the while loop terminates. We do need to reset it at the end. From 39b5f6a8f1f445b7f2f69fce4495ce7ae6c9b84f Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 5 Aug 2022 17:24:29 -0500 Subject: [PATCH 186/414] Add RotheDiagram --- src/sage/combinat/diagram.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 3165d13112f..77b7da27035 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -54,7 +54,7 @@ from sage.categories.sets_cat import Sets from sage.sets.non_negative_integers import NonNegativeIntegers as NN from sage.combinat.partition import Partition -from sage.combinat.permutation import Permutation +from sage.combinat.permutation import Permutations from sage.structure.unique_representation import UniqueRepresentation from sage.structure.list_clone import ClonableArray from sage.structure.parent import Parent @@ -588,3 +588,25 @@ def _an_element_(self): Element = NorthwestDiagram + +def RotheDiagram(w): + r""" + A constructor to build the Rothe diagram of a permutation w as an element + in the parent of :class:`NorthwestDiagrams`. + + EXAMPLES:: + + sage: w = Permutations(9)([1, 7, 4, 5, 9, 3, 2, 8, 6]) + sage: from sage.combinat.diagram import RotheDiagram + sage: D = RotheDiagram(w) + """ + + from sage.misc.mrange import cartesian_product_iterator + + if not w in Permutations(): + raise ValueError('w must be a Permutation') + + cells = [c for c in cartesian_product_iterator((range(9),range(9))) + if c[0]+1 < w.inverse()(c[1]+1) and c[1]+1 < w(c[0]+1)] + + return NorthwestDiagram(cells) From cc85d22d4b868623e81543cce2bfbca2223ffa16 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 5 Aug 2022 19:09:59 -0500 Subject: [PATCH 187/414] Add rothe_diagram, n_reduced_words, and Stanley symmetric function --- src/sage/combinat/permutation.py | 35 +++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index f6d802e2beb..94748effe15 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -220,7 +220,8 @@ - Travis Scrimshaw (2014-02-05): Made :class:`StandardPermutations_n` a finite Weyl group to make it more uniform with :class:`SymmetricGroup`. Added ability to compute the conjugacy classes. - +- Trevor K. Karn (2022-08-05): Add :meth:`Permutation.n_reduced_words` and + :meth:`Permutation.stanley_symmetric_function`. - Amrutha P, Shriya M, Divya Aggarwal (2022-08-16): Added Multimajor Index. Classes and methods @@ -229,6 +230,7 @@ # **************************************************************************** # Copyright (C) 2007 Mike Hansen +# 2022 Trevor K. Karn # 2022 Amrutha P # 2022 Shriya M <25shriya@gmail.com> # 2022 Divya Aggarwal @@ -2993,6 +2995,32 @@ def reduced_word_lexmin(self): return rw + def rothe_diagram(self): + r""" + Return the Rothe diagram of ``self``. + """ + from sage.combinat.diagram import RotheDiagram + return RotheDiagram(self) + + def n_reduced_words(self): + r""" + Return the number of reduced words of ``self`` without explicitly + computing them all. + """ + Tx = self.rothe_diagram().peelable_tableaux() + + return sum(map(_tableau_contribution, Tx)) + + def stanley_symmetric_function(self): + r""" + Return the Stanley symmetric function associated to ``self``. + """ + + from sage.combinat.sf.sf import SymmetricFunctions + from sage.rings.rational_field import RationalField as QQ + + s = SymmetricFunctions(QQ).s() + return sum(s[T.shape()] for T in self.rothe_diagram().peelable_tableaux()) ################ # Fixed Points # @@ -5242,6 +5270,11 @@ def shifted_shuffle(self, other): return self.shifted_concatenation(other, "right").\ right_permutohedron_interval(self.shifted_concatenation(other, "left")) +def _tableau_contribution(T): + r""" + Get the number of SYT of shape(``T``). + """ + return(StandardTableaux(T.shape()).cardinality()) ################################################################ # Parent classes ################################################################ From 1bf86402ab293535b22730b1c05c3610bbe77d97 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 5 Aug 2022 20:28:00 -0500 Subject: [PATCH 188/414] Add documentation/examples --- src/sage/combinat/diagram.py | 204 +++++++++++++++++++++++++++++++++-- 1 file changed, 195 insertions(+), 9 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 77b7da27035..148bc89a685 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -35,6 +35,122 @@ . . . . . . . . . . . . . O +We can also check if certain cells are contained in a given diagram:: + + sage: (1, 0) in T + True + sage: (2, 2) in T + False + +A ``Diagram`` is an element of the parent class ``Diagrams``, so we can also +construct a diagram by calling an instance of ``Diagrams``:: + + sage: from sage.combinat.diagram import Diagrams + sage: Dgms = Diagrams() + sage: D in Dgms + True + sage: Dgms([(0,1),(3,3)].pp() + . . . . + . O . . + . . . . + . . . O + +There are two other specific types of diagrams which are implemented in Sage, +namely northwest diagrams (:class:`NorthwestDiagram`) and Rothe diagrams +(:func:`RotheDiagram`, a special kind of northwest diagram). + +A diagram is a +*northwest diagram* if it satsifies the property that: the presence of two +cells `(i_1, j_1)` and `(i_2, j_2)` in a diagram `D` implies the presence of +the cell `(\min(i_1, i_2), \min(j_1, j_2))`:: + + sage: from sage.combinat.diagram import NorthwestDiagram + sage: N = NorthwestDiagram([(0,0), (0, 10), (5,0)]); N.pp() + O . . . . . . . . . O + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + +Note that checking whether or not the northwest property is satisfied is +automatically checked. The diagram found by adding the cell `(1,1)` to the +diagram above is *not* a northwest diagram. The cell `(1,0)` should be +present due to the presence of `(5,0)` and `(1,1)`:: + + sage: Diagram([(0, 0), (0, 10), (5, 0), (1, 1)]).pp() + O . . . . . . . . . O + . O . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + sage: NorthwestDiagram([(0, 0), (0, 10), (5, 0), (1, 1)]) + Traceback (most recent call last): + ... + ValueError + +However, this behavior can be turned off if you are confident that you are +providing a northwest diagram:: + + sage: N = NorthwestDiagram([(0, 0), (0, 10), (5, 0), + ....: (1, 1), (0, 1), (1, 0)], + ....: check=False) + sage: N.pp() + O O . . . . . . . . O + O O . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + +Note that arbitrary diagrams which happen to be northwest diagrams only live +in the parent of :class:`Diagrams`:: + + sage: D = Diagram([(0, 0), (0, 10), (5, 0), (1, 1), (0, 1), (1, 0)]) + sage: D.pp() + O O . . . . . . . . O + O O . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + sage: D in NorthwestDiagrams() + False + +For a fixed northwest diagram `D`, we say that a Young tableau `T` is +`D`-peelable if the row indices of the cells in the first column of `D` are +the entries in an initial segment in the first column of `T`, and the + +One particular way of constructing a northwest diagram from a permutation is +by constructing its Rothe diagram. Formally, if `\omega` is a permutation, +then the Rothe diagram `D(\omega)` is the diagram whose cells are + +.. MATH:: + + D(\omega) = \{(\omega_i, j) : i Date: Mon, 8 Aug 2022 17:15:17 -0700 Subject: [PATCH 189/414] Add examples and documentation --- src/sage/combinat/diagram.py | 209 ++++++++++++++++++++++++++++++----- 1 file changed, 181 insertions(+), 28 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 148bc89a685..cffd4ce4af8 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -37,11 +37,28 @@ We can also check if certain cells are contained in a given diagram:: - sage: (1, 0) in T + sage: (1, 0) in D True - sage: (2, 2) in T + sage: (2, 2) in D False +If you know that there are entire empty rows or columns at the end of the +diagram, you can manually pass them with keyword arguments ``n_rows=`` or +``n_cols=``:: + + sage: Diagram([(0,0), (0,3), (2,2), (2,4)]).pp() + O . . O . + . . . . . + . . O . O + sage: Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6).pp() + O . . O . . + . . . . . . + . . O . O . + . . . . . . + . . . . . . + . . . . . . + + A ``Diagram`` is an element of the parent class ``Diagrams``, so we can also construct a diagram by calling an instance of ``Diagrams``:: @@ -49,10 +66,10 @@ sage: Dgms = Diagrams() sage: D in Dgms True - sage: Dgms([(0,1),(3,3)].pp() - . . . . + sage: Dgms([(0,1),(3,3)]).pp() . O . . . . . . + . . . . . . . O There are two other specific types of diagrams which are implemented in Sage, @@ -88,7 +105,7 @@ sage: NorthwestDiagram([(0, 0), (0, 10), (5, 0), (1, 1)]) Traceback (most recent call last): ... - ValueError + AssertionError However, this behavior can be turned off if you are confident that you are providing a northwest diagram:: @@ -115,20 +132,17 @@ . . . . . . . . . . . . . . . . . . . . . . O . . . . . . . . . . + sage: from sage.combinat.diagram import NorthwestDiagrams sage: D in NorthwestDiagrams() False -For a fixed northwest diagram `D`, we say that a Young tableau `T` is -`D`-peelable if the row indices of the cells in the first column of `D` are -the entries in an initial segment in the first column of `T`, and the - One particular way of constructing a northwest diagram from a permutation is by constructing its Rothe diagram. Formally, if `\omega` is a permutation, then the Rothe diagram `D(\omega)` is the diagram whose cells are .. MATH:: - D(\omega) = \{(\omega_i, j) : i \omega_j \}. Informally, one can construct the Rothe diagram by starting with all `n^2` possible cells, and then deleting the cells `(i, \omega(i))` as well as all @@ -145,11 +159,47 @@ O . . . . . . . O . O O . . . . O . O . . . . . - . . O . . . . . . . . . . . . . - . . O . . . . . - . . O . . . . . - . . O . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . + +For a fixed northwest diagram `D`, we say that a Young tableau `T` is +`D`-peelable if: + +- the row indices of the cells in the first column of `D` are +the entries in an initial segment in the first column of `T` and + +- the tableau `Q` obtained by removing those cells from `T` and playing jeu de +taquin is `D-C`-peelable, where `D-C` is the diagram formed by forgetting the +first column of `D`. + +Reiner and Shimozono [RS1995]_ showed that the number `\operatorname{red}(w)` +of reduced words of a permutation `w` may be computed using the peelable +tableaux of the Rothe diagram `D(w)`. Explicitly, + +.. MATH:: + + \operatorname{red}(w) = \sum_{T} f_{\operatorname{shape} T} + +where the sum runs over the `D(w)`-peelable tableaux `T` and `f_\lambda` is the +number of standard Young tableaux of shape `\lambda` (which may be computed +using the hook-length formula). + +We can compute the `D`-peelable diagrams for a northwest diagram `D`:: + + sage: cells = [(0,0), (0,1), (0,2), (1,0), (2,0), (2,2), (2,4), + ....: (4,0), (4,2)] + sage: D = NorthwestDiagram(cells); D.pp() + O O O . . + O . . . . + O . O . O + . . . . . + O . O . . + sage: D.peelable_tableaux() + {[[1, 1, 1], [2, 3, 3], [3, 5], [5]], + [[1, 1, 1, 3], [2, 3], [3, 5], [5]]} AUTHORS: @@ -157,7 +207,7 @@ """ # **************************************************************************** -# Copyright (C) 2013 Trevor K. Karn +# Copyright (C) 2022 Trevor K. Karn # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -166,7 +216,6 @@ # https://www.gnu.org/licenses/ # **************************************************************************** - from sage.categories.sets_cat import Sets from sage.sets.non_negative_integers import NonNegativeIntegers as NN from sage.combinat.partition import Partition @@ -184,7 +233,8 @@ class Diagram(ClonableArray): EXAMPLES:: sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) + sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]); D + [(0, 0), (0, 3), (2, 2), (2, 4)] TESTS:: @@ -292,6 +342,13 @@ def pp(self): O . . O . . . . . . . . O . O + sage: Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6).pp() + O . . O . . + . . . . . . + . . O . O . + . . . . . . + . . . . . . + . . . . . . """ output_str = '' @@ -395,7 +452,8 @@ def n_cells(self): def check(self): r""" Check that this is a valid diagram by checking that it is an iterable - of length-two tuples of integers. + of length-two tuples. (The fact that each tuple is length-two is + implicitly checked during creation of the diagram). .. WARNING:: @@ -408,8 +466,30 @@ def check(self): sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) sage: D.check() + + In the next two examples, the diagram ``D`` is initialized but with + bad information that is not detected untill we call ``D.check()``. + The first example fails because one cells is indexed by negative + integers:: + + sage: D = Diagram([(0,0), (0,-3), (2,2), (2,4)]) + sage: D.check() + Traceback (most recent call last): + ... + AssertionError + + The next example fails because one cell is indexed by rational + numbers:: + + sage: D = Diagram([(0,0), (0,3), (2/3,2), (2,4)]) + sage: D.check() + Traceback (most recent call last): + ... + AssertionError """ - assert all(isinstance(c, tuple) and len(c) == 2 for c in self._cells) + from sage.sets.non_negative_integers import NonNegativeIntegers + NN = NonNegativeIntegers() + assert all(all(list(i in NN for i in c)) for c in self._cells.keys()) class Diagrams(UniqueRepresentation, Parent): @@ -417,7 +497,17 @@ class Diagrams(UniqueRepresentation, Parent): The class of combinatorial diagrams. A *combinatorial diagram* is a set of cells indexed by pairs of natural - numbers. + numbers. Calling an instance of :class:`Diagrams` is one way to construct + diagrams. + + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagrams + sage: Dgms = Diagrams() + sage: D = Dgms([(0,0), (0,3), (2,2), (2,4)]) + sage: D.parent() + Combinatorial diagrams + """ def __init__(self): r""" @@ -492,15 +582,64 @@ class NorthwestDiagram(Diagram): called *northwest diagrams*. For general diagrams see :class:`Diagram`. + + EXAMPLES:: + + sage: from sage.combinat.diagram import NorthwestDiagram + sage: N = NorthwestDiagram([(0,0), (0, 2), (2,0)]) + + To visualize them, use the ``.pp()`` method:: + + sage: N.pp() + O . O + . . . + O . . + + One can also create northwest diagrams from a partition:: + + sage: mu = Partition([5,4,3,2,1]) + sage: mu.pp() + ***** + **** + *** + ** + * + sage: D = NorthwestDiagram(mu.cells()).pp() + O O O O O + O O O O . + O O O . . + O O . . . + O . . . . """ @staticmethod def __classcall_private__(self, cells, **kwargs): - r""" + """ + Normalize input to ensure a correct parent. + + EXAMPLES:: + + sage: from sage.combinat.diagram import NorthwestDiagram + sage: N1 = NorthwestDiagram([(0,1), (0,2)]) + sage: N2 = NorthwestDiagram([(0,1), (0,3)]) + sage: N1.parent() is N2.parent() + True """ # TODO: Assert that cells is sorted in lex order to speed up lookup. return NorthwestDiagrams()(cells, **kwargs) def __init__(self, cells, **kwargs): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.combinat.diagram import NorthwestDiagram + sage: N = NorthwestDiagram([(0,1), (0,2)]) + + TESTS:: + + sage: TestSuite(N).run() + """ check = kwargs.get('check', True) @@ -511,13 +650,14 @@ def check(self): r""" A diagram has the northwest property if the presence of cells `(i1, j1)` and `(i2, j2)` implies the presence of the cell - `(min(i1, i2), min(j1, j2))` + `(min(i1, i2), min(j1, j2))`. This method checks if the northwest + property is satisfied for ``self`` - .. WARNING:: + EXAMPLES:: - This method is required for `Diagram` to be a subclass of - :class:`~sage.structure.list_clone.ClonableArray`, however the check - is *not* automatically performed upon creation of the element. + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) + sage: D.check() """ from itertools import combinations assert all((min(i1,i2), min(j1,j2)) in self @@ -785,6 +925,17 @@ def RotheDiagram(w): . . . . . . . . . . . . . . O . . . . . . . . . . . . + + + Currently, only elements of the parent + :class:`sage.combinat.permutations.Permutations` are supported. In + particular, elements of permutation groups are not supported:: + + sage: w = SymmetricGroup(9).an_element() + sage: RotheDiagram(w) + Traceback (most recent call last): + ... + ValueError: w must be a Permutation """ from sage.misc.mrange import cartesian_product_iterator @@ -792,7 +943,9 @@ def RotheDiagram(w): if not w in Permutations(): raise ValueError('w must be a Permutation') - cells = [c for c in cartesian_product_iterator((range(9),range(9))) + N = w.size() + + cells = [c for c in cartesian_product_iterator((range(N),range(N))) if c[0]+1 < w.inverse()(c[1]+1) and c[1]+1 < w(c[0]+1)] - return NorthwestDiagram(cells, check=False) + return NorthwestDiagram(cells, n_rows=N, n_cols=N, check=False) From 93c5bb719cdc3381cbca5322e9c7619c8c2fa150 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 8 Aug 2022 18:29:15 -0700 Subject: [PATCH 190/414] PEP8 and example --- src/sage/combinat/diagram.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index cffd4ce4af8..9d8f8fa62b6 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -56,7 +56,7 @@ . . O . O . . . . . . . . . . . . . - . . . . . . + . . . . . . A ``Diagram`` is an element of the parent class ``Diagrams``, so we can also @@ -226,6 +226,7 @@ from sage.combinat.tableau import Tableau from sage.combinat.skew_tableau import SkewTableaux + class Diagram(ClonableArray): r""" A class to model arbitrary combinatorial diagrams. @@ -286,8 +287,8 @@ def __init__(self, cells, **kwargs): self._cells = {c: True for c in cells} self._n_rows = kwargs.pop('n_rows', max(c[0] for c in self._cells) + 1) self._n_cols = kwargs.pop('n_cols', max(c[1] for c in self._cells) + 1) - self._n_nonempty_rows = len(set(i for i,j in self._cells)) - self._n_nonempty_cols = len(set(j for i,j in self._cells)) + self._n_nonempty_rows = len(set(i for i, j in self._cells)) + self._n_nonempty_cols = len(set(j for i, j in self._cells)) ClonableArray.__init__(self, Diagrams(), cells, check=False) @@ -452,7 +453,7 @@ def n_cells(self): def check(self): r""" Check that this is a valid diagram by checking that it is an iterable - of length-two tuples. (The fact that each tuple is length-two is + of length-two tuples. (The fact that each tuple is length-two is implicitly checked during creation of the diagram). .. WARNING:: @@ -468,7 +469,7 @@ def check(self): sage: D.check() In the next two examples, the diagram ``D`` is initialized but with - bad information that is not detected untill we call ``D.check()``. + bad information that is not detected untill we call ``D.check()``. The first example fails because one cells is indexed by negative integers:: @@ -487,7 +488,7 @@ def check(self): ... AssertionError """ - from sage.sets.non_negative_integers import NonNegativeIntegers + from sage.sets.non_negative_integers import NonNegativeIntegers NN = NonNegativeIntegers() assert all(all(list(i in NN for i in c)) for c in self._cells.keys()) @@ -660,8 +661,8 @@ def check(self): sage: D.check() """ from itertools import combinations - assert all((min(i1,i2), min(j1,j2)) in self - for (i1, j1), (i2, j2) in combinations(self._cells, 2)) + assert all((min(i1, i2), min(j1, j2)) in self + for (i1, j1), (i2, j2) in combinations(self._cells, 2)) def peelable_tableaux(self): r""" @@ -777,6 +778,10 @@ def peelable_tableaux(self): . . . . . . . . O O . O . . sage: D.peelable_tableaux() + {[[1, 1, 1, 1, 1], [2, 2, 2, 3, 3], [3, 3, 3], [5, 5, 5]], + [[1, 1, 1, 1, 1], [2, 2, 2, 3, 3], [3, 3, 3, 5], [5, 5]], + [[1, 1, 1, 1, 1, 3], [2, 2, 2, 3], [3, 3, 3], [5, 5, 5]], + [[1, 1, 1, 1, 1, 3], [2, 2, 2, 3], [3, 3, 3, 5], [5, 5]]} .. ALGORITHM:: @@ -790,9 +795,9 @@ def peelable_tableaux(self): # if there is a single column in the diagram then there is only # one posslbe peelable tableau. if self._n_nonempty_cols == 1: - return {Tableau([[i+1] for i,j in self._cells])} + return {Tableau([[i+1] for i, j in self._cells])} - first_col = min(j for i,j in self._cells) + first_col = min(j for i, j in self._cells) # TODO: The next two lines of code could be optimized by only # looping over self._cells once (rather than two separate times) @@ -902,7 +907,6 @@ def _an_element_(self): """ return self([(0, 1), (0, 2), (1, 1), (2, 3)]) - Element = NorthwestDiagram @@ -927,7 +931,7 @@ def RotheDiagram(w): . . . . . . . . . - Currently, only elements of the parent + Currently, only elements of the parent :class:`sage.combinat.permutations.Permutations` are supported. In particular, elements of permutation groups are not supported:: @@ -940,12 +944,12 @@ def RotheDiagram(w): from sage.misc.mrange import cartesian_product_iterator - if not w in Permutations(): + if w not in Permutations(): raise ValueError('w must be a Permutation') N = w.size() - cells = [c for c in cartesian_product_iterator((range(N),range(N))) + cells = [c for c in cartesian_product_iterator((range(N), range(N))) if c[0]+1 < w.inverse()(c[1]+1) and c[1]+1 < w(c[0]+1)] return NorthwestDiagram(cells, n_rows=N, n_cols=N, check=False) From f1864a3faa782dd8e44e56cb5fb86de79ba8e2b8 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 8 Aug 2022 18:46:43 -0700 Subject: [PATCH 191/414] Fix __init__ --- src/sage/combinat/diagram.py | 45 +++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 9d8f8fa62b6..af6862c0db0 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -285,11 +285,23 @@ def __init__(self, cells, **kwargs): . . O . . """ self._cells = {c: True for c in cells} - self._n_rows = kwargs.pop('n_rows', max(c[0] for c in self._cells) + 1) - self._n_cols = kwargs.pop('n_cols', max(c[1] for c in self._cells) + 1) + if n_rows is not None: + if n_rows < max(c[0] for c in self._cells) + 1: + raise ValueError('n_rows is too small') + self._n_rows = n_rows + else: + self._n_rows = max(c[0] for c in self._cells) + 1 + if n_cols is not None: + if n_cols < max(c[1] for c in self._cells) + 1: + raise ValueError('n_cols is too small') + self._n_cols = n_cols + else: + self._n_cols = max(c[1] for c in self._cells) + 1 + self._n_nonempty_rows = len(set(i for i, j in self._cells)) self._n_nonempty_cols = len(set(j for i, j in self._cells)) + ClonableArray.__init__(self, Diagrams(), cells, check=False) def _hash_(self): @@ -628,7 +640,7 @@ def __classcall_private__(self, cells, **kwargs): # TODO: Assert that cells is sorted in lex order to speed up lookup. return NorthwestDiagrams()(cells, **kwargs) - def __init__(self, cells, **kwargs): + def __init__(self, cells, n_rows=None, n_cols=None, check=True): r""" Initialize ``self``. @@ -640,11 +652,32 @@ def __init__(self, cells, **kwargs): TESTS:: sage: TestSuite(N).run() + sage: N = NorthwestDiagram([(0,1), (0,2)], n_cols = 1) + Traceback (most recent call last): + ... + ValueError: n_cols is too small + sage: N = NorthwestDiagram([(0,0), (1,0)], n_rows = 1) + Traceback (most recent call last): + ... + ValueError: n_rows is too small """ + self._cells = {c: True for c in cells} + if n_rows is not None: + if n_rows < max(c[0] for c in self._cells) + 1: + raise ValueError('n_rows is too small') + self._n_rows = n_rows + else: + self._n_rows = max(c[0] for c in self._cells) + 1 + if n_cols is not None: + if n_cols < max(c[1] for c in self._cells) + 1: + raise ValueError('n_cols is too small') + self._n_cols = n_cols + else: + self._n_cols = max(c[1] for c in self._cells) + 1 - check = kwargs.get('check', True) + self._n_nonempty_rows = len(set(i for i, j in self._cells)) + self._n_nonempty_cols = len(set(j for i, j in self._cells)) - Diagram.__init__(self, cells, **kwargs) ClonableArray.__init__(self, NorthwestDiagrams(), cells, check=check) def check(self): @@ -880,7 +913,7 @@ class :class:`NorthwestDiagrams`. diagram itself. """ - def __repr__(self): + def _repr_(self): r""" EXAMPLES:: From f893d43d49be88cf7d64bda44a0c5f293d6008f0 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 9 Aug 2022 12:32:01 -0700 Subject: [PATCH 192/414] Fix kwargs, repr, and remove double .__init__ call --- src/sage/combinat/diagram.py | 86 ++++++++++------------------ src/sage/combinat/diagram_minimal.py | 26 +++++++++ 2 files changed, 55 insertions(+), 57 deletions(-) create mode 100644 src/sage/combinat/diagram_minimal.py diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index af6862c0db0..c2738971a78 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -225,9 +225,10 @@ from sage.structure.parent import Parent from sage.combinat.tableau import Tableau from sage.combinat.skew_tableau import SkewTableaux +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass -class Diagram(ClonableArray): +class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): r""" A class to model arbitrary combinatorial diagrams. @@ -244,7 +245,7 @@ class Diagram(ClonableArray): sage: TestSuite(D).run() """ @staticmethod - def __classcall_private__(self, cells, **kwargs): + def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=False): r""" Normalize the input so that it lives in the correct parent. @@ -255,9 +256,9 @@ def __classcall_private__(self, cells, **kwargs): sage: D.parent() Combinatorial diagrams """ - return Diagrams()(cells, **kwargs) + return Diagrams()(cells, n_rows, n_cols, check) - def __init__(self, cells, **kwargs): + def __init__(self, parent, cells, n_rows=None, n_cols=None, check=False): r""" EXAMPLES:: @@ -301,8 +302,7 @@ def __init__(self, cells, **kwargs): self._n_nonempty_rows = len(set(i for i, j in self._cells)) self._n_nonempty_cols = len(set(j for i, j in self._cells)) - - ClonableArray.__init__(self, Diagrams(), cells, check=False) + ClonableArray.__init__(self, parent, cells, check=False) def _hash_(self): r""" @@ -332,7 +332,7 @@ def __contains__(self, other): """ return other in self._cells - def __repr__(self): + def _repr_(self): r""" EXAMPLES:: @@ -537,7 +537,7 @@ def __init__(self): Parent.__init__(self, category=Sets()) - def __repr__(self): + def _repr_(self): r""" EXAMPLES:: @@ -547,7 +547,7 @@ def __repr__(self): """ return 'Combinatorial diagrams' - def _element_constructor_(self, cells, **kwargs): + def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=False): r""" EXAMPLES:: @@ -562,7 +562,7 @@ def _element_constructor_(self, cells, **kwargs): sage: TestSuite(Dgms).run() """ - return self.element_class(cells, **kwargs) + return self.element_class(self, cells, n_rows, n_cols, check) def _an_element_(self): r""" @@ -586,7 +586,7 @@ def _an_element_(self): # Northwest diagrams #################### -class NorthwestDiagram(Diagram): +class NorthwestDiagram(Diagram, metaclass=InheritComparisonClasscallMetaclass): r""" A diagram is a set of cells indexed by natural numbers. Such a diagram has the *northwest property* if the presence of cells `(i1, j1)` and @@ -625,60 +625,25 @@ class NorthwestDiagram(Diagram): O . . . . """ @staticmethod - def __classcall_private__(self, cells, **kwargs): + def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=True): """ Normalize input to ensure a correct parent. EXAMPLES:: - sage: from sage.combinat.diagram import NorthwestDiagram + sage: from sage.combinat.diagram import NorthwestDiagram, NorthwestDiagrams sage: N1 = NorthwestDiagram([(0,1), (0,2)]) sage: N2 = NorthwestDiagram([(0,1), (0,3)]) sage: N1.parent() is N2.parent() True + sage: N3 = NorthwestDiagrams()([(0,1), (0,2)]) + sage: N3.parent() is NorthwestDiagrams() + True + sage: N1.parent() is NorthwestDiagrams() + True """ # TODO: Assert that cells is sorted in lex order to speed up lookup. - return NorthwestDiagrams()(cells, **kwargs) - - def __init__(self, cells, n_rows=None, n_cols=None, check=True): - r""" - Initialize ``self``. - - EXAMPLES:: - - sage: from sage.combinat.diagram import NorthwestDiagram - sage: N = NorthwestDiagram([(0,1), (0,2)]) - - TESTS:: - - sage: TestSuite(N).run() - sage: N = NorthwestDiagram([(0,1), (0,2)], n_cols = 1) - Traceback (most recent call last): - ... - ValueError: n_cols is too small - sage: N = NorthwestDiagram([(0,0), (1,0)], n_rows = 1) - Traceback (most recent call last): - ... - ValueError: n_rows is too small - """ - self._cells = {c: True for c in cells} - if n_rows is not None: - if n_rows < max(c[0] for c in self._cells) + 1: - raise ValueError('n_rows is too small') - self._n_rows = n_rows - else: - self._n_rows = max(c[0] for c in self._cells) + 1 - if n_cols is not None: - if n_cols < max(c[1] for c in self._cells) + 1: - raise ValueError('n_cols is too small') - self._n_cols = n_cols - else: - self._n_cols = max(c[1] for c in self._cells) + 1 - - self._n_nonempty_rows = len(set(i for i, j in self._cells)) - self._n_nonempty_cols = len(set(j for i, j in self._cells)) - - ClonableArray.__init__(self, NorthwestDiagrams(), cells, check=check) + return NorthwestDiagrams()(cells, n_rows, n_cols, check) def check(self): r""" @@ -689,9 +654,16 @@ def check(self): EXAMPLES:: - sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) - sage: D.check() + sage: from sage.combinat.diagram import NorthwestDiagram + sage: N = NorthwestDiagram([(0,0), (0,3), (3,0)]) + sage: N.check() + + Here is a non-example:: + + sage: notN = NorthwestDiagram([(0,1), (1,0)]) #.check() is implicit + Traceback (most recent call last): + ... + AssertionError """ from itertools import combinations assert all((min(i1, i2), min(j1, j2)) in self diff --git a/src/sage/combinat/diagram_minimal.py b/src/sage/combinat/diagram_minimal.py new file mode 100644 index 00000000000..d7fea533651 --- /dev/null +++ b/src/sage/combinat/diagram_minimal.py @@ -0,0 +1,26 @@ +from sage.categories.sets_cat import Sets +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.list_clone import ClonableArray +from sage.structure.parent import Parent +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass + +class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): + @staticmethod + def __classcall_private__(cls, cells): + return Diagrams()(cells) + + def __init__(self, parent, cells): + self._cells = {c: True for c in cells} + ClonableArray.__init__(self, parent, cells) + + def check(self): + pass + +class Diagrams(UniqueRepresentation, Parent): + def __init__(self): + Parent.__init__(self, category=Sets()) + + def _element_constructor_(self, cells): + return self.element_class(self, cells) + + Element = Diagram \ No newline at end of file From cd5df3fcae0534b1c6d19da2d6e37cda5948817f Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 9 Aug 2022 12:49:23 -0700 Subject: [PATCH 193/414] Fix accidental check=False --- src/sage/combinat/diagram.py | 5 ++--- src/sage/combinat/diagram_minimal.py | 26 -------------------------- 2 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 src/sage/combinat/diagram_minimal.py diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index c2738971a78..6eeb1d82399 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -302,7 +302,7 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=False): self._n_nonempty_rows = len(set(i for i, j in self._cells)) self._n_nonempty_cols = len(set(j for i, j in self._cells)) - ClonableArray.__init__(self, parent, cells, check=False) + ClonableArray.__init__(self, parent, cells, check) def _hash_(self): r""" @@ -373,7 +373,7 @@ def pp(self): output_str += '. ' output_str += '\n' - print(output_str) + print(output_str, end='') # don't double up on `\n`'ss def n_rows(self): r""" @@ -935,7 +935,6 @@ def RotheDiagram(w): . . . . . O . . . . . . . . . . . . - Currently, only elements of the parent :class:`sage.combinat.permutations.Permutations` are supported. In particular, elements of permutation groups are not supported:: diff --git a/src/sage/combinat/diagram_minimal.py b/src/sage/combinat/diagram_minimal.py deleted file mode 100644 index d7fea533651..00000000000 --- a/src/sage/combinat/diagram_minimal.py +++ /dev/null @@ -1,26 +0,0 @@ -from sage.categories.sets_cat import Sets -from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.list_clone import ClonableArray -from sage.structure.parent import Parent -from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass - -class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): - @staticmethod - def __classcall_private__(cls, cells): - return Diagrams()(cells) - - def __init__(self, parent, cells): - self._cells = {c: True for c in cells} - ClonableArray.__init__(self, parent, cells) - - def check(self): - pass - -class Diagrams(UniqueRepresentation, Parent): - def __init__(self): - Parent.__init__(self, category=Sets()) - - def _element_constructor_(self, cells): - return self.element_class(self, cells) - - Element = Diagram \ No newline at end of file From 261983ad0473a18a967afb34d039bd1e9e7605d4 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 9 Aug 2022 14:48:19 -0700 Subject: [PATCH 194/414] Add references and more examples. All doctests pass --- src/doc/en/reference/combinat/module_list.rst | 1 + src/doc/en/reference/references/index.rst | 5 +- src/sage/combinat/diagram.py | 191 +++++++++++++++--- 3 files changed, 164 insertions(+), 33 deletions(-) diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index 7a97a40e30d..93201401ddb 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -109,6 +109,7 @@ Comprehensive Module List sage/combinat/designs/steiner_quadruple_systems sage/combinat/designs/subhypergraph_search sage/combinat/designs/twographs + sage/combinat/diagram sage/combinat/diagram_algebras sage/combinat/dlx sage/combinat/dyck_word diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index e6a0e5c2000..ee5166235ab 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5100,7 +5100,10 @@ REFERENCES: **6** (1997), 59-87. .. [RSS] :wikipedia:`Residual_sum_of_squares`, accessed 13th - October 2009. + October 2009. + +.. [RS1995] Victor Reiner, Mark Shimozono, "Plactification", + J. Algebraic Combin. **4** (1995), 331-351. .. [RS2012] G. Rudolph and M. Schmidt, "Differential Geometry and Mathematical Physics. Part I. Manifolds, Lie Groups and Hamiltonian Systems", Springer, 2012. diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 6eeb1d82399..10103b92a6f 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -146,8 +146,8 @@ Informally, one can construct the Rothe diagram by starting with all `n^2` possible cells, and then deleting the cells `(i, \omega(i))` as well as all -cells to the right and below. (These are the "death rays" of []_.) To compute -a Rothe diagram in Sage, start with a +cells to the right and below. (These are sometimes called "death rays".) To +compute a Rothe diagram in Sage, start with a :class:`~sage.combinat.permutation.Permutation`:: sage: w = Permutations(8)([2,5,4,1,3,6,7,8]) @@ -219,6 +219,7 @@ from sage.categories.sets_cat import Sets from sage.sets.non_negative_integers import NonNegativeIntegers as NN from sage.combinat.partition import Partition +from sage.combinat.skew_partition import SkewPartition from sage.combinat.permutation import Permutations from sage.structure.unique_representation import UniqueRepresentation from sage.structure.list_clone import ClonableArray @@ -607,27 +608,16 @@ class NorthwestDiagram(Diagram, metaclass=InheritComparisonClasscallMetaclass): O . O . . . O . . - - One can also create northwest diagrams from a partition:: - - sage: mu = Partition([5,4,3,2,1]) - sage: mu.pp() - ***** - **** - *** - ** - * - sage: D = NorthwestDiagram(mu.cells()).pp() - O O O O O - O O O O . - O O O . . - O O . . . - O . . . . """ @staticmethod - def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=True): + def __classcall_private__(self, cells, n_rows=None, n_cols=None, + check=True): """ - Normalize input to ensure a correct parent. + Normalize input to ensure a correct parent. This method also allows + one to specify whether or not to check the northwest property for the + provided cells. Note that the default behavior is to check for the + northwest property (``check=True``), while in arbitrary diagrams, the + default behavior is that ``check=False``. EXAMPLES:: @@ -683,7 +673,7 @@ def peelable_tableaux(self): sage: NWD.peelable_tableaux() {[[1], [3]]} - From [...]_ we know that there is only one peelable tableau for the + From [RS1995]_ we know that there is only one peelable tableau for the Rothe diagram of the permutation (in one line notation) `251643`:: sage: D = NorthwestDiagram([(1, 2), (1, 3), (3, 2), (3, 3), (4, 2)]) @@ -791,7 +781,7 @@ def peelable_tableaux(self): .. ALGORITHM:: This implementation uses the algorithm suggested in remark 25 - of [...]_. + of [RS1995]_. """ # TODO: There is a condition on the first column (if the rows in Dhat # are a subset of the rows in the first column) which simplifies the @@ -849,11 +839,23 @@ class NorthwestDiagrams(Diagrams): One may create an instance of a northwest diagram either by directly calling :class:`NorthwestDiagram` or by creating an instance of the - parent class `NorthwestDiagrams` (with an `s`) and calling it on a `list` - of tuples consisting of the coordinates of the diagram. + parent class ``NorthwestDiagrams`` (with an `s`) and calling it on a + ``list`` of ``tuple``s consisting of the coordinates of the diagram. EXAMPLES:: + sage: from sage.combinat.diagram import NorthwestDiagram, NorthwestDiagrams + sage: D = NorthwestDiagram([(0,1), (0,2), (1,1)]); D.pp() + . O O + . O . + sage: NWDgms = NorthwestDiagrams() + sage: D = NWDgms([(1,1), (1,2), (2,1)]); D.pp() + . . . + . O O + . O . + sage: D.parent() + Combinatorial northwest diagrams + Additionally, there are natural constructions of a northwest diagram given the data of a permutation (Rothe diagrams are the protypical example of northwest diagrams), or the data of a partition of an integer, or a @@ -867,22 +869,51 @@ class NorthwestDiagrams(Diagrams): \{ (i, j) : i < \omega^{-1}(j) \text{ and } j < \omega(i)} We can construct one by calling :meth:`rothe_diagram` method on the parent - class :class:`NorthwestDiagrams`. - - EXAMPLES:: + class :class:`NorthwestDiagrams`:: - sage: from sage.combinat.diagram import NorthwestDiagrams - sage: NWDgms = NorthwestDiagrams + sage: w = Permutations(4)([4,3,2,1]) + sage: NorthwestDiagrams().rothe_diagram(w).pp() + O O O . + O O . . + O . . . + . . . . To turn a Ferrers diagram into a northwest diagram, we may call the - :meth:`ferrers_diagram` method. This will return a Ferrer's diagram in the + :meth:`from_partition` method. This will return a Ferrer's diagram in the parent of all northwest diagrams. For many use-cases it is probably better to get Ferrer's diagrams by the corresponding method on partitons, namely - :meth:`sage.combinat.partitions.Partitions.ferrers_diagram`. + :meth:`sage.combinat.partitions.Partitions.ferrers_diagram`:: + + sage: mu = Partition([7,3,1,1]) + sage: mu.pp() + ******* + *** + * + * + sage: NorthwestDiagrams().from_partition(mu).pp() + O O O O O O O + O O O . . . . + O . . . . . . + O . . . . . . It is also possible to turn a Ferrers diagram of a skew partition into a northwest diagram, altough it is more subtle than just using the skew - diagram itself. + diagram itself. One must first reflect the partition about a vertical axis + so that the skew partition looks "backwards":: + + sage: mu, nu = Partition([5,4,3,2,1]), Partition([3,2,1]) + sage: s = mu/nu; s.pp() + ** + ** + ** + ** + * + sage: NorthwestDiagrams().from_skew_partition(s).pp() + O O . . . + . O O . . + . . O O . + . . . O O + . . . . O """ def _repr_(self): @@ -912,6 +943,102 @@ def _an_element_(self): """ return self([(0, 1), (0, 2), (1, 1), (2, 3)]) + def rothe_diagram(self, w): + r""" + Return the Rothe diagram of ``w``. + + EXAMPLES:: + + sage: w = Permutations(3)([2,1,3]) + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: NorthwestDiagrams().rothe_diagram(w).pp() + O . . + . . . + . . . + """ + return RotheDiagram(w) + + def from_partition(self, mu): + r""" + Return the Ferrer's diagram of ``mu`` as an element of the parent + ``NorthwestDiagrams`` + + EXAMPLES:: + + sage: mu = Partition([5,2,1]); mu.pp() + ***** + ** + * + sage: mu.parent() + Partitions + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: D = NorthwestDiagrams().from_partition(mu) + sage: D.pp() + O O O O O + O O . . . + O . . . . + sage: D.parent() + Combinatorial northwest diagrams + + TESTS:: + + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: mu = [5, 2, 1] + sage: D = NorthwestDiagrams().from_partition(mu) + Traceback (most recent call last): + ... + ValueError: mu must be a Partition + """ + if not isinstance(mu, Partition): + raise ValueError("mu must be a Partition") + return self.element_class(self, mu.cells(), check=False) + + def from_skew_partition(self, s): + r""" + Get the northwest diagram found by reflecting a skew shape across + a vertical plane. + + EXAMPLES:: + + sage: mu, nu = Partition([3,2,1]), Partition([2,1]) + sage: s = mu/nu; s.pp() + * + * + * + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: D = NorthwestDiagrams().from_skew_partition(s) + sage: D.pp() + O . . + . O . + . . O + + sage: mu, nu = Partition([3,3,2]), Partition([2,2,2]) + sage: s = mu/nu; s.pp() + * + * + sage: NorthwestDiagrams().from_skew_partition(s).pp() + O . . + O . . + . . . + + TESTS:: + + sage: mu = Partition([3,2,1]) + sage: NorthwestDiagrams().from_skew_partition(mu) + Traceback (most recent call last): + ... + ValueError: mu must be a SkewPartition + """ + if not isinstance(s, SkewPartition): + raise ValueError("mu must be a SkewPartition") + + n_cols = s.outer()[0] + n_rows = len(s.outer()) + + cells = [(i, n_cols - 1 - j) for i, j in s.cells()] + + return self.element_class(self, cells, n_rows, n_cols, check=False) + Element = NorthwestDiagram From 8c5bc2bea123f2e6cdfa7832cf5c4cb4696b8f1b Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 9 Aug 2022 14:51:30 -0700 Subject: [PATCH 195/414] Fix a typo in Rothe diagrams --- src/sage/combinat/diagram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 10103b92a6f..e0fcbd0afe0 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -866,7 +866,7 @@ class NorthwestDiagrams(Diagrams): .. MATH:: - \{ (i, j) : i < \omega^{-1}(j) \text{ and } j < \omega(i)} + D(\omega) = \{(\omega_j, i) : i \omega_j \}. We can construct one by calling :meth:`rothe_diagram` method on the parent class :class:`NorthwestDiagrams`:: From 507aad05110451daef19ae3a6e80cc37b8295b7f Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 10 Aug 2022 08:43:38 -0700 Subject: [PATCH 196/414] Fix bug in identity partition/empty diagram --- src/sage/combinat/diagram.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index e0fcbd0afe0..c7328f69541 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -287,18 +287,27 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=False): . . O . . """ self._cells = {c: True for c in cells} + + if self._cells: + # minimum possible number of rows/cols + N_rows = max(c[0] for c in self._cells) + N_cols = max(c[1] for c in self._cells) + else: # if there are no cells + N_rows = 0 + N_cols = 0 + if n_rows is not None: - if n_rows < max(c[0] for c in self._cells) + 1: + if n_rows < N_rows + 1: raise ValueError('n_rows is too small') self._n_rows = n_rows else: - self._n_rows = max(c[0] for c in self._cells) + 1 + self._n_rows = N_rows + 1 if n_cols is not None: - if n_cols < max(c[1] for c in self._cells) + 1: + if n_cols < N_cols + 1: raise ValueError('n_cols is too small') self._n_cols = n_cols else: - self._n_cols = max(c[1] for c in self._cells) + 1 + self._n_cols = N_cols + 1 self._n_nonempty_rows = len(set(i for i, j in self._cells)) self._n_nonempty_cols = len(set(j for i, j in self._cells)) @@ -1071,6 +1080,18 @@ def RotheDiagram(w): Traceback (most recent call last): ... ValueError: w must be a Permutation + + + TESTS:: + + sage: w = Permutations(5)([1,2,3,4,5]) + sage: from sage.combinat.diagram import RotheDiagram + sage: RotheDiagram(w).pp() + . . . . . + . . . . . + . . . . . + . . . . . + . . . . . """ from sage.misc.mrange import cartesian_product_iterator From 0f5ee00ed03905893d73d8593b0cc001d12bf984 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Thu, 11 Aug 2022 13:43:45 -0700 Subject: [PATCH 197/414] Speed up sliding by skipping classcall --- src/sage/combinat/skew_tableau.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 0578404a643..840a6864d0f 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -1039,7 +1039,7 @@ def backward_slide(self, corner=None): # the while loop terminates. We do need to reset it at the end. new_st[i][j] = None - return SkewTableau(new_st) + return SkewTableaux()(new_st) reverse_slide = backward_slide From cc0c83c0312cdba1bc252d17e218de0c974784b8 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Thu, 11 Aug 2022 17:43:04 -0700 Subject: [PATCH 198/414] Remove unneeded creation of skew tableaux --- src/sage/combinat/diagram.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index c7328f69541..7b4aa40563a 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -818,12 +818,11 @@ def peelable_tableaux(self): for Q in Dhat.peelable_tableaux(): # get the vertical strips mu = Q.shape() - vertical_strips = mu.add_vertical_border_strip(k) - for s in vertical_strips: + vertical_strip_cells = mu.vertical_border_strip_cells(k) + for s in vertical_strip_cells: sQ = SkewTableaux()(Q) # sQ is skew - get it? - new_cells = (s/mu).cells() # perform the jeu de taquin slides - for c in new_cells: + for c in s: sQ = sQ.backward_slide(c) # create the new tableau by filling the columns sQ_new = sQ.to_list() From a145dabb1350d923a7bb353df7c231812b170676 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 16 Aug 2022 10:56:59 -0700 Subject: [PATCH 199/414] First round of reviewer suggestions --- src/sage/combinat/diagram.py | 74 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 7b4aa40563a..c0ef71ec74b 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -58,20 +58,6 @@ . . . . . . . . . . . . - -A ``Diagram`` is an element of the parent class ``Diagrams``, so we can also -construct a diagram by calling an instance of ``Diagrams``:: - - sage: from sage.combinat.diagram import Diagrams - sage: Dgms = Diagrams() - sage: D in Dgms - True - sage: Dgms([(0,1),(3,3)]).pp() - . O . . - . . . . - . . . . - . . . O - There are two other specific types of diagrams which are implemented in Sage, namely northwest diagrams (:class:`NorthwestDiagram`) and Rothe diagrams (:func:`RotheDiagram`, a special kind of northwest diagram). @@ -267,9 +253,9 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=False): sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) sage: D1.cells() [(0, 2), (0, 3), (1, 1), (3, 2)] - sage: D1.n_rows() + sage: D1.nrows() 4 - sage: D1.n_cols() + sage: D1.ncols() 4 We can specify the number of rows and columns explicitly, @@ -278,7 +264,7 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=False): sage: D2 = Diagram([(0,2),(0,3),(1,1),(3,2)], n_cols=5) sage: D2.cells() [(0, 2), (0, 3), (1, 1), (3, 2)] - sage: D2.n_cols() + sage: D2.ncols() 5 sage: D2.pp() . . O O . @@ -286,7 +272,7 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=False): . . . . . . . O . . """ - self._cells = {c: True for c in cells} + self._cells = frozenset(cells) if self._cells: # minimum possible number of rows/cols @@ -350,7 +336,7 @@ def _repr_(self): sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]); D [(0, 2), (0, 3), (1, 1), (3, 2)] """ - return str(list(self._cells)) + return str(sorted(self._cells)) def pp(self): r""" @@ -385,7 +371,7 @@ def pp(self): print(output_str, end='') # don't double up on `\n`'ss - def n_rows(self): + def number_of_rows(self): r""" Return the total number of rows of the cell, including those which do not have any cells. @@ -397,14 +383,16 @@ def n_rows(self): sage: from sage.combinat.diagram import Diagram sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) - sage: D1.n_rows() + sage: D1.number_of_rows() + 4 + sage: D1.nrows() 4 We can also include empty rows at the end:: sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)], n_rows=6) - sage: D.n_rows() + sage: D.number_of_rows() 6 sage: D.pp() . . O O @@ -416,7 +404,9 @@ def n_rows(self): """ return self._n_rows - def n_cols(self): + nrows = number_of_rows + + def number_of_cols(self): r""" Return the total number of rows of the cell, including those which do not have any cells. @@ -428,14 +418,16 @@ def n_cols(self): sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]) - sage: D.n_cols() + sage: D.number_of_cols() + 4 + sage: D.ncols() 4 We can also include empty columns at the end:: sage: from sage.combinat.diagram import Diagram sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)], n_cols=6) - sage: D.n_cols() + sage: D.number_of_cols() 6 sage: D.pp() . . O O . . @@ -446,6 +438,8 @@ def n_cols(self): return self._n_cols + ncols = number_of_cols + def cells(self): r""" Return a ``list`` of the cells contained in the diagram ``self``. @@ -457,9 +451,9 @@ def cells(self): sage: D1.cells() [(0, 2), (0, 3), (1, 1), (3, 2)] """ - return list(self._cells.keys()) + return sorted(self._cells) - def n_cells(self): + def number_of_cells(self): r""" Return the total number of cells contained in the diagram ``self``. @@ -467,11 +461,17 @@ def n_cells(self): sage: from sage.combinat.diagram import Diagram sage: D1 = Diagram([(0,2),(0,3),(1,1),(3,2)]) + sage: D1.number_of_cells() + 4 sage: D1.n_cells() 4 """ return len(self._cells) + n_cells = number_of_cells + + size = number_of_cells + def check(self): r""" Check that this is a valid diagram by checking that it is an iterable @@ -499,7 +499,7 @@ def check(self): sage: D.check() Traceback (most recent call last): ... - AssertionError + ValueError: Diagrams must be indexed by non-negative integers The next example fails because one cell is indexed by rational numbers:: @@ -508,11 +508,12 @@ def check(self): sage: D.check() Traceback (most recent call last): ... - AssertionError + ValueError: Diagrams must be indexed by non-negative integers """ from sage.sets.non_negative_integers import NonNegativeIntegers NN = NonNegativeIntegers() - assert all(all(list(i in NN for i in c)) for c in self._cells.keys()) + if not all(all(list(i in NN for i in c)) for c in self._cells): + raise ValueError("Diagrams must be indexed by non-negative integers") class Diagrams(UniqueRepresentation, Parent): @@ -557,7 +558,7 @@ def _repr_(self): """ return 'Combinatorial diagrams' - def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=False): + def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=True): r""" EXAMPLES:: @@ -619,14 +620,12 @@ class NorthwestDiagram(Diagram, metaclass=InheritComparisonClasscallMetaclass): O . . """ @staticmethod - def __classcall_private__(self, cells, n_rows=None, n_cols=None, - check=True): + def __classcall_private__(self, cells, n_rows=None, + n_cols=None, check=True): """ Normalize input to ensure a correct parent. This method also allows one to specify whether or not to check the northwest property for the - provided cells. Note that the default behavior is to check for the - northwest property (``check=True``), while in arbitrary diagrams, the - default behavior is that ``check=False``. + provided cells. EXAMPLES:: @@ -641,7 +640,6 @@ def __classcall_private__(self, cells, n_rows=None, n_cols=None, sage: N1.parent() is NorthwestDiagrams() True """ - # TODO: Assert that cells is sorted in lex order to speed up lookup. return NorthwestDiagrams()(cells, n_rows, n_cols, check) def check(self): @@ -799,7 +797,7 @@ def peelable_tableaux(self): # if there is a single column in the diagram then there is only # one posslbe peelable tableau. if self._n_nonempty_cols == 1: - return {Tableau([[i+1] for i, j in self._cells])} + return {Tableau([[i+1] for i, j in self.cells()])} first_col = min(j for i, j in self._cells) From 9870a889e4243df61629aff4c45f848f943b9f4d Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 16 Aug 2022 11:56:50 -0700 Subject: [PATCH 200/414] Assertion -> ValueError --- src/sage/combinat/diagram.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index c0ef71ec74b..5437d35b728 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -91,7 +91,7 @@ sage: NorthwestDiagram([(0, 0), (0, 10), (5, 0), (1, 1)]) Traceback (most recent call last): ... - AssertionError + ValueError: diagram is not northwest However, this behavior can be turned off if you are confident that you are providing a northwest diagram:: @@ -660,11 +660,20 @@ def check(self): sage: notN = NorthwestDiagram([(0,1), (1,0)]) #.check() is implicit Traceback (most recent call last): ... - AssertionError + ValueError: diagram is not northwest + + TESTS:: + + sage: NorthwestDiagram([(0,1/2)]) + Traceback (most recent call last): + ... + ValueError: Diagrams must be indexed by non-negative integers """ from itertools import combinations - assert all((min(i1, i2), min(j1, j2)) in self - for (i1, j1), (i2, j2) in combinations(self._cells, 2)) + Diagram.check(self) + if not all((min(i1, i2), min(j1, j2)) in self + for (i1, j1), (i2, j2) in combinations(self._cells, 2)): + raise ValueError("diagram is not northwest") def peelable_tableaux(self): r""" From 40ae181d23859f9d86b42cabf4e76dd68c9cc090 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 16 Aug 2022 12:32:13 -0700 Subject: [PATCH 201/414] Add tests and remove Stanley s.f. --- src/sage/combinat/permutation.py | 45 ++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 94748effe15..48b188aacc7 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -2998,14 +2998,31 @@ def reduced_word_lexmin(self): def rothe_diagram(self): r""" Return the Rothe diagram of ``self``. + + EXAMPLES:: + + sage: p = Permutation([4,2,1,3]) + sage: D = p.rothe_diagram(); D + [(0, 0), (0, 1), (0, 2), (1, 0)] + sage: D.pp() + O O O . + O . . . + . . . . + . . . . """ from sage.combinat.diagram import RotheDiagram return RotheDiagram(self) - def n_reduced_words(self): + def number_of_reduced_words(self): r""" Return the number of reduced words of ``self`` without explicitly computing them all. + + EXAMPLES:: + + sage: p = Permutation([6,4,2,5,1,8,3,7]) + sage: len(p.reduced_words()) == p.number_of_reduced_words() + True """ Tx = self.rothe_diagram().peelable_tableaux() @@ -3014,13 +3031,22 @@ def n_reduced_words(self): def stanley_symmetric_function(self): r""" Return the Stanley symmetric function associated to ``self``. - """ - from sage.combinat.sf.sf import SymmetricFunctions - from sage.rings.rational_field import RationalField as QQ + EXAMPLES:: + sage: p = Permutation([4,5,2,3,1]) + sage: p.stanley_symmetric_function() + 56*m[1, 1, 1, 1, 1, 1, 1, 1] + 30*m[2, 1, 1, 1, 1, 1, 1] + + 16*m[2, 2, 1, 1, 1, 1] + 9*m[2, 2, 2, 1, 1] + 6*m[2, 2, 2, 2] + + 10*m[3, 1, 1, 1, 1, 1] + 5*m[3, 2, 1, 1, 1] + 3*m[3, 2, 2, 1] + + m[3, 3, 1, 1] + m[3, 3, 2] + 2*m[4, 1, 1, 1, 1] + m[4, 2, 1, 1] + + m[4, 2, 2] + """ + from sage.combinat.sf.sf import SymmetricFunctions + from sage.rings.rational_field import QQ s = SymmetricFunctions(QQ).s() - return sum(s[T.shape()] for T in self.rothe_diagram().peelable_tableaux()) + m = SymmetricFunctions(QQ).m() + return m(sum(s[T.shape()] for T in self.rothe_diagram().peelable_tableaux())) ################ # Fixed Points # @@ -5273,8 +5299,17 @@ def shifted_shuffle(self, other): def _tableau_contribution(T): r""" Get the number of SYT of shape(``T``). + + EXAMPLES:: + + sage: T = Tableau([[1,1,1],[2]]) + sage: from sage.combinat.permutation import _tableau_contribution + sage: _tableau_contribution(T) + 3 """ + from sage.combinat.tableau import StandardTableaux return(StandardTableaux(T.shape()).cardinality()) + ################################################################ # Parent classes ################################################################ From 9e4963781e99617bf38a2e691b618ea7e0e449a1 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 17 Aug 2022 13:51:57 -0500 Subject: [PATCH 202/414] Add methods to interact with polyominos/compositions --- src/sage/combinat/diagram.py | 109 ++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 5437d35b728..8aa037a43e3 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -245,7 +245,7 @@ def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=False): """ return Diagrams()(cells, n_rows, n_cols, check) - def __init__(self, parent, cells, n_rows=None, n_cols=None, check=False): + def __init__(self, parent, cells, n_rows=None, n_cols=None, check=True): r""" EXAMPLES:: @@ -590,6 +590,86 @@ def _an_element_(self): """ return self([(0, 2), (1, 1), (2, 3)]) + def from_polyomino(self, p): + r""" + Create the diagram corresponding to a 2d + :class:`~sage.combinat.tiling.Polyomino.` + + EXAMPLES:: + + sage: from sage.combinat.tiling import Polyomino + sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) + sage: from sage.combinat.diagram import Diagrams + sage: Diagrams().from_polyomino(p).pp() + O . . + O O O + sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue') + sage: Diagrams().from_polyomino(p) + Traceback (most recent call last): + ... + ValueError: Dimension of the polyomino must be 2 + """ + if not p._dimension == 2: + raise ValueError("Dimension of the polyomino must be 2") + cells = map(tuple, list(p)) + return self.element_class(self, cells) + + def from_composition(self, alpha): + r""" + Create the diagram corresponding to a weak composition `alpha \vDash n`. + + EXAMPLES:: + + sage: alpha = Composition([3,0,2,1,4,4]) + sage: from sage.combinat.diagram import Diagrams + sage: Diagrams().from_composition(alpha).pp() + O O O . + . . . . + O O . . + O . . . + O O O O + O O O O + """ + cells = [] + for i, n in enumerate(alpha): + cells.extend((i, j) for j in range(n)) + return self.element_class(self, cells, check=False) + + def from_zero_one_matrix(self, M, check=True): + r""" + Get a diagram from a matrix with entries in `\{0, 1\}`, where + positions of cells are indicated by the `1`'s. + + EXAMPLES:: + + sage: M = matrix([[1,0,1,1],[0,1,1,0]]) + sage: from sage.combinat.diagram import Diagrams + sage: Diagrams().from_zero_one_matrix(M).pp() + O . O O + . O O . + + sage: M = matrix([[1, 0, 0], [1, 0, 0], [0, 0, 0]]) + sage: Diagrams().from_zero_one_matrix(M).pp() + O . . + O . . + . . . + + """ + # check matrix is zero-one + n_rows, n_cols = M.dimensions() + + zero = M.base_ring().zero() + one = M.base_ring().one() + + if check: + for i in range(n_rows): + for j in range(n_cols): + if not (M[i,j] == zero or M[i,j] == one): + raise ValueError("Matrix entries must be 0 or 1") + cells = [(i, j) for i in range(n_rows) for j in range(n_cols) if M[i,j]] + + return self.element_class(self, cells, n_rows, n_cols, check=False) + Element = Diagram @@ -970,9 +1050,15 @@ def rothe_diagram(self, w): O . . . . . . . . + sage: NorthwestDiagrams().from_permutation(w).pp() + O . . + . . . + . . . """ return RotheDiagram(w) + from_permutation = rothe_diagram + def from_partition(self, mu): r""" Return the Ferrer's diagram of ``mu`` as an element of the parent @@ -1054,6 +1140,27 @@ def from_skew_partition(self, s): return self.element_class(self, cells, n_rows, n_cols, check=False) + def from_parallelogram_polyomino(self, p): + r""" + Create the diagram corresponding to a + :class:`~sage.combinat.parallelogram_polyomino.ParallelogramPolyomino`. + + EXAMPLES:: + + sage: p = ParallelogramPolyomino([[0, 0, 1, 0, 0, 0, 1, 1], + ....: [1, 1, 0, 1, 0, 0, 0, 0]]) + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: NorthwestDiagrams().from_parallelogram_polyomino(p).pp() + O O . + O O O + . O O + . O O + . O O + """ + from sage.matrix.constructor import Matrix + M = Matrix(p.get_array()) + return self.from_zero_one_matrix(M) + Element = NorthwestDiagram From 5f1dc60af6dc579a035a38c2fcd2946cef81f385 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 17:24:59 -0500 Subject: [PATCH 203/414] Move docmentation to class level --- src/sage/combinat/diagram.py | 409 ++++++++++++++++++----------------- 1 file changed, 207 insertions(+), 202 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 8aa037a43e3..cbd88846a26 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -2,190 +2,12 @@ Combinatorial diagrams A combinatorial diagram is a collection of cells `(i,j)` indexed by pairs of -natural numbers. The positions are indexed by rows and columns. For example, -a Ferrer's diagram is a diagram obtained from a partition -`\lambda = (\lambda_0, \lambda_1, \ldots, \lambda_\ell)` where the cells are -in rows `i` for `0 \leq i \leq \ell` and the cells in row `i` consist of -`(i,j)` for `0 \leq j < \lambda_i`. In English notation, the indices are read -from top left to bottom right as in a matrix. - -Indexing conventions are the same as -:class:`~sage.combinat.partition.Partition`. - -EXAMPLES: - -To create an arbirtrary diagram, pass a list of all cells:: - - sage: from sage.combinat.diagram import Diagram - sage: cells = [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] - sage: D = Diagram(cells); D - [(0, 0), (0, 1), (1, 0), (1, 1), (4, 4), (4, 5), (4, 6), (5, 4), (7, 6)] - -We can visualize the diagram by printing ``O``'s and ``.``'s. ``O``'s are -present in the cells which are present in the diagram and a ``.`` represents -the absence of a cell in the diagram:: - - sage: D.pp() - O O . . . . . - O O . . . . . - . . . . . . . - . . . . . . . - . . . . O O O - . . . . O . . - . . . . . . . - . . . . . . O - -We can also check if certain cells are contained in a given diagram:: - - sage: (1, 0) in D - True - sage: (2, 2) in D - False - -If you know that there are entire empty rows or columns at the end of the -diagram, you can manually pass them with keyword arguments ``n_rows=`` or -``n_cols=``:: - - sage: Diagram([(0,0), (0,3), (2,2), (2,4)]).pp() - O . . O . - . . . . . - . . O . O - sage: Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6).pp() - O . . O . . - . . . . . . - . . O . O . - . . . . . . - . . . . . . - . . . . . . - -There are two other specific types of diagrams which are implemented in Sage, -namely northwest diagrams (:class:`NorthwestDiagram`) and Rothe diagrams -(:func:`RotheDiagram`, a special kind of northwest diagram). - -A diagram is a -*northwest diagram* if it satsifies the property that: the presence of two -cells `(i_1, j_1)` and `(i_2, j_2)` in a diagram `D` implies the presence of -the cell `(\min(i_1, i_2), \min(j_1, j_2))`:: - - sage: from sage.combinat.diagram import NorthwestDiagram - sage: N = NorthwestDiagram([(0,0), (0, 10), (5,0)]); N.pp() - O . . . . . . . . . O - . . . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - O . . . . . . . . . . - -Note that checking whether or not the northwest property is satisfied is -automatically checked. The diagram found by adding the cell `(1,1)` to the -diagram above is *not* a northwest diagram. The cell `(1,0)` should be -present due to the presence of `(5,0)` and `(1,1)`:: - - sage: Diagram([(0, 0), (0, 10), (5, 0), (1, 1)]).pp() - O . . . . . . . . . O - . O . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - O . . . . . . . . . . - sage: NorthwestDiagram([(0, 0), (0, 10), (5, 0), (1, 1)]) - Traceback (most recent call last): - ... - ValueError: diagram is not northwest - -However, this behavior can be turned off if you are confident that you are -providing a northwest diagram:: - - sage: N = NorthwestDiagram([(0, 0), (0, 10), (5, 0), - ....: (1, 1), (0, 1), (1, 0)], - ....: check=False) - sage: N.pp() - O O . . . . . . . . O - O O . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - O . . . . . . . . . . - -Note that arbitrary diagrams which happen to be northwest diagrams only live -in the parent of :class:`Diagrams`:: - - sage: D = Diagram([(0, 0), (0, 10), (5, 0), (1, 1), (0, 1), (1, 0)]) - sage: D.pp() - O O . . . . . . . . O - O O . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - . . . . . . . . . . . - O . . . . . . . . . . - sage: from sage.combinat.diagram import NorthwestDiagrams - sage: D in NorthwestDiagrams() - False - -One particular way of constructing a northwest diagram from a permutation is -by constructing its Rothe diagram. Formally, if `\omega` is a permutation, -then the Rothe diagram `D(\omega)` is the diagram whose cells are - -.. MATH:: - - D(\omega) = \{(\omega_j, i) : i \omega_j \}. - -Informally, one can construct the Rothe diagram by starting with all `n^2` -possible cells, and then deleting the cells `(i, \omega(i))` as well as all -cells to the right and below. (These are sometimes called "death rays".) To -compute a Rothe diagram in Sage, start with a -:class:`~sage.combinat.permutation.Permutation`:: - - sage: w = Permutations(8)([2,5,4,1,3,6,7,8]) - -Then call :func:`RotheDiagram` on ``w``:: - - sage: from sage.combinat.diagram import RotheDiagram - sage: RotheDiagram(w).pp() - O . . . . . . . - O . O O . . . . - O . O . . . . . - . . . . . . . . - . . . . . . . . - . . . . . . . . - . . . . . . . . - . . . . . . . . - -For a fixed northwest diagram `D`, we say that a Young tableau `T` is -`D`-peelable if: - -- the row indices of the cells in the first column of `D` are -the entries in an initial segment in the first column of `T` and - -- the tableau `Q` obtained by removing those cells from `T` and playing jeu de -taquin is `D-C`-peelable, where `D-C` is the diagram formed by forgetting the -first column of `D`. - -Reiner and Shimozono [RS1995]_ showed that the number `\operatorname{red}(w)` -of reduced words of a permutation `w` may be computed using the peelable -tableaux of the Rothe diagram `D(w)`. Explicitly, - -.. MATH:: - - \operatorname{red}(w) = \sum_{T} f_{\operatorname{shape} T} - -where the sum runs over the `D(w)`-peelable tableaux `T` and `f_\lambda` is the -number of standard Young tableaux of shape `\lambda` (which may be computed -using the hook-length formula). - -We can compute the `D`-peelable diagrams for a northwest diagram `D`:: - - sage: cells = [(0,0), (0,1), (0,2), (1,0), (2,0), (2,2), (2,4), - ....: (4,0), (4,2)] - sage: D = NorthwestDiagram(cells); D.pp() - O O O . . - O . . . . - O . O . O - . . . . . - O . O . . - sage: D.peelable_tableaux() - {[[1, 1, 1], [2, 3, 3], [3, 5], [5]], - [[1, 1, 1, 3], [2, 3], [3, 5], [5]]} +natural numbers. + +For arbitrary diagrams, see :class:`Diagram`. There are also two other specific +types of diagrams implemented here. They are northwest diagrams +(:class:`NorthwestDiagram`) and Rothe diagrams (:func:`RotheDiagram`, a special +kind of northwest diagram). AUTHORS: @@ -217,13 +39,64 @@ class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): r""" - A class to model arbitrary combinatorial diagrams. + Combinatorial diagrams with positions indexed by rows in columns. - EXAMPLES:: + The positions are indexed by rows and columns as in a matrix. For example, + a Ferrer's diagram is a diagram obtained from a partition + `\lambda = (\lambda_0, \lambda_1, \ldots, \lambda_\ell)` where the cells are + in rows `i` for `0 \leq i \leq \ell` and the cells in row `i` consist of + `(i,j)` for `0 \leq j < \lambda_i`. In English notation, the indices are + read from top left to bottom right as in a matrix. + + Indexing conventions are the same as + :class:`~sage.combinat.partition.Partition`. Printing the diagram of a + partition, however, will always be in English notation. + + EXAMPLES: + + To create an arbirtrary diagram, pass a list of all cells:: sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]); D - [(0, 0), (0, 3), (2, 2), (2, 4)] + sage: cells = [(0,0), (0,1), (1,0), (1,1), (4,4), (4,5), (4,6), (5,4), (7, 6)] + sage: D = Diagram(cells); D + [(0, 0), (0, 1), (1, 0), (1, 1), (4, 4), (4, 5), (4, 6), (5, 4), (7, 6)] + + We can visualize the diagram by printing ``O``'s and ``.``'s. ``O``'s are + present in the cells which are present in the diagram and a ``.`` represents + the absence of a cell in the diagram:: + + sage: D.pp() + O O . . . . . + O O . . . . . + . . . . . . . + . . . . . . . + . . . . O O O + . . . . O . . + . . . . . . . + . . . . . . O + + We can also check if certain cells are contained in a given diagram:: + + sage: (1, 0) in D + True + sage: (2, 2) in D + False + + If you know that there are entire empty rows or columns at the end of the + diagram, you can manually pass them with keyword arguments ``n_rows=`` or + ``n_cols=``:: + + sage: Diagram([(0,0), (0,3), (2,2), (2,4)]).pp() + O . . O . + . . . . . + . . O . O + sage: Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6).pp() + O . . O . . + . . . . . . + . . O . O . + . . . . . . + . . . . . . + . . . . . . TESTS:: @@ -469,7 +342,7 @@ def number_of_cells(self): return len(self._cells) n_cells = number_of_cells - + size = number_of_cells def check(self): @@ -757,8 +630,45 @@ def check(self): def peelable_tableaux(self): r""" - Given a northwest diagram `D`, a tableau `T` is said to be - `D`-peelable if... + For a fixed northwest diagram `D`, we say that a Young tableau `T` is + `D`-peelable if: + + - the row indices of the cells in the first column of `D` are + the entries in an initial segment in the first column of `T` and + + - the tableau `Q` obtained by removing those cells from `T` and playing + jeu de taquin is `D-C`-peelable, where `D-C` is the diagram formed by + forgetting the first column of `D`. + + Reiner and Shimozono [RS1995]_ showed that the number + `\operatorname{red}(w)` of reduced words of a permutation `w` may be + computed using the peelable tableaux of the Rothe diagram `D(w)`. + Explicitly, + + .. MATH:: + + \operatorname{red}(w) = \sum_{T} f_{\operatorname{shape} T} + + where the sum runs over the `D(w)`-peelable tableaux `T` and `f_\lambda` + is the number of standard Young tableaux of shape `\lambda` (which may + be computed using the hook-length formula). + + EXAMPLES: + + We can compute the `D`-peelable diagrams for a northwest diagram `D`:: + + sage: from sage.combinat.diagram import NorthwestDiagram + sage: cells = [(0,0), (0,1), (0,2), (1,0), (2,0), (2,2), (2,4), + ....: (4,0), (4,2)] + sage: D = NorthwestDiagram(cells); D.pp() + O O O . . + O . . . . + O . O . O + . . . . . + O . O . . + sage: D.peelable_tableaux() + {[[1, 1, 1], [2, 3, 3], [3, 5], [5]], + [[1, 1, 1, 3], [2, 3], [3, 5], [5]]} EXAMPLES: @@ -925,20 +835,73 @@ def peelable_tableaux(self): class NorthwestDiagrams(Diagrams): r""" - The class of northwest diagrams. - - A diagram has the *northwest property* if the presence of cells - `(i1, j1)` and `(i2, j2)` implies the presence of the cell - `(min(i1, i2), min(j1, j2))`. For the class of general diagrams, see - :class:`Diagrams`. + Diagrams satisfying the northwest property. - One may create an instance of a northwest diagram either by directly - calling :class:`NorthwestDiagram` or by creating an instance of the - parent class ``NorthwestDiagrams`` (with an `s`) and calling it on a - ``list`` of ``tuple``s consisting of the coordinates of the diagram. + A diagram is a + *northwest diagram* if it satsifies the property that: the presence of two + cells `(i_1, j_1)` and `(i_2, j_2)` in a diagram `D` implies the presence of + the cell `(\min(i_1, i_2), \min(j_1, j_2))`. EXAMPLES:: + sage: from sage.combinat.diagram import NorthwestDiagram + sage: N = NorthwestDiagram([(0,0), (0, 10), (5,0)]); N.pp() + O . . . . . . . . . O + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + + Note that checking whether or not the northwest property is satisfied is + automatically checked. The diagram found by adding the cell `(1,1)` to the + diagram above is *not* a northwest diagram. The cell `(1,0)` should be + present due to the presence of `(5,0)` and `(1,1)`:: + + sage: from sage.combinat.diagram import Diagram + sage: Diagram([(0, 0), (0, 10), (5, 0), (1, 1)]).pp() + O . . . . . . . . . O + . O . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + sage: NorthwestDiagram([(0, 0), (0, 10), (5, 0), (1, 1)]) + Traceback (most recent call last): + ... + ValueError: diagram is not northwest + + However, this behavior can be turned off if you are confident that you are + providing a northwest diagram:: + + sage: N = NorthwestDiagram([(0, 0), (0, 10), (5, 0), + ....: (1, 1), (0, 1), (1, 0)], + ....: check=False) + sage: N.pp() + O O . . . . . . . . O + O O . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + + Note that arbitrary diagrams which happen to be northwest diagrams only live + in the parent of :class:`Diagrams`:: + + sage: D = Diagram([(0, 0), (0, 10), (5, 0), (1, 1), (0, 1), (1, 0)]) + sage: D.pp() + O O . . . . . . . . O + O O . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + . . . . . . . . . . . + O . . . . . . . . . . + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: D in NorthwestDiagrams() + False + + Here are some more examples:: + sage: from sage.combinat.diagram import NorthwestDiagram, NorthwestDiagrams sage: D = NorthwestDiagram([(0,1), (0,2), (1,1)]); D.pp() . O O @@ -1042,6 +1005,35 @@ def rothe_diagram(self, w): r""" Return the Rothe diagram of ``w``. + One particular way of constructing a northwest diagram from a permutation is + by constructing its Rothe diagram. Formally, if `\omega` is a permutation, + then the Rothe diagram `D(\omega)` is the diagram whose cells are + + .. MATH:: + + D(\omega) = \{(\omega_j, i) : i \omega_j \}. + + Informally, one can construct the Rothe diagram by starting with all + `n^2` possible cells, and then deleting the cells `(i, \omega(i))` as + well as all cells to the right and below. (These are sometimes called + "death rays".) To compute a Rothe diagram in Sage, start with a + :class:`~sage.combinat.permutation.Permutation`:: + + sage: w = Permutations(8)([2,5,4,1,3,6,7,8]) + + Then call :func:`RotheDiagram` on ``w``:: + + sage: from sage.combinat.diagram import RotheDiagram + sage: RotheDiagram(w).pp() + O . . . . . . . + O . O O . . . . + O . O . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . + EXAMPLES:: sage: w = Permutations(3)([2,1,3]) @@ -1081,6 +1073,19 @@ def from_partition(self, mu): sage: D.parent() Combinatorial northwest diagrams + This will print in English notation even if the notation is set to + French for the partition. + + sage: Partitions.options.convention="french" + sage: mu.pp() + * + ** + ***** + sage: D.pp() + O O O O O + O O . . . + O . . . . + TESTS:: sage: from sage.combinat.diagram import NorthwestDiagrams @@ -1133,7 +1138,7 @@ def from_skew_partition(self, s): if not isinstance(s, SkewPartition): raise ValueError("mu must be a SkewPartition") - n_cols = s.outer()[0] + n_cols = s.outer()[0] n_rows = len(s.outer()) cells = [(i, n_cols - 1 - j) for i, j in s.cells()] From 1042fb79feb17f67d96a96a892a45872ab8e8f18 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 17:43:19 -0500 Subject: [PATCH 204/414] Default check -> true + documentation change --- src/sage/combinat/diagram.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index cbd88846a26..f801f9d2787 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -105,7 +105,7 @@ class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): sage: TestSuite(D).run() """ @staticmethod - def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=False): + def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=True): r""" Normalize the input so that it lives in the correct parent. @@ -363,13 +363,11 @@ def check(self): sage: D = Diagram([(0,0), (0,3), (2,2), (2,4)]) sage: D.check() - In the next two examples, the diagram ``D`` is initialized but with - bad information that is not detected untill we call ``D.check()``. + In the next two examples, a bad diagram is passed. The first example fails because one cells is indexed by negative integers:: sage: D = Diagram([(0,0), (0,-3), (2,2), (2,4)]) - sage: D.check() Traceback (most recent call last): ... ValueError: Diagrams must be indexed by non-negative integers @@ -378,7 +376,6 @@ def check(self): numbers:: sage: D = Diagram([(0,0), (0,3), (2/3,2), (2,4)]) - sage: D.check() Traceback (most recent call last): ... ValueError: Diagrams must be indexed by non-negative integers From 60470dbe79396d611c87e452dfbe96df29285eb0 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 17:43:41 -0500 Subject: [PATCH 205/414] Clean up documentation --- src/sage/combinat/diagram.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index f801f9d2787..22171731c0b 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -213,7 +213,9 @@ def _repr_(self): def pp(self): r""" - Return a visualization of the diagram. Cells which are present in the + Return a visualization of the diagram. + + Cells which are present in the diagram are filled with a ``O``. Cells which are not present in the diagram are filled with a ``.``. @@ -246,8 +248,7 @@ def pp(self): def number_of_rows(self): r""" - Return the total number of rows of the cell, including those which do - not have any cells. + Return the total number of rows of the cell. EXAMPLES: @@ -261,6 +262,7 @@ def number_of_rows(self): sage: D1.nrows() 4 + The total number of rows includes including those which are empty. We can also include empty rows at the end:: sage: from sage.combinat.diagram import Diagram @@ -281,8 +283,7 @@ def number_of_rows(self): def number_of_cols(self): r""" - Return the total number of rows of the cell, including those which do - not have any cells. + Return the total number of rows of the cell. EXAMPLES: @@ -347,15 +348,7 @@ def number_of_cells(self): def check(self): r""" - Check that this is a valid diagram by checking that it is an iterable - of length-two tuples. (The fact that each tuple is length-two is - implicitly checked during creation of the diagram). - - .. WARNING:: - - This method is required for ``Diagram`` to be a subclass of - :class:`~sage.structure.list_clone.ClonableArray`, however the check - is *not* automatically performed upon creation of the element. + Check that this is a valid diagram. EXAMPLES:: @@ -549,6 +542,8 @@ def from_zero_one_matrix(self, M, check=True): class NorthwestDiagram(Diagram, metaclass=InheritComparisonClasscallMetaclass): r""" + Diagrams with the northwest property. + A diagram is a set of cells indexed by natural numbers. Such a diagram has the *northwest property* if the presence of cells `(i1, j1)` and `(i2, j2)` implies the presence of the cell @@ -1050,8 +1045,7 @@ def rothe_diagram(self, w): def from_partition(self, mu): r""" - Return the Ferrer's diagram of ``mu`` as an element of the parent - ``NorthwestDiagrams`` + Return the Ferrer's diagram of ``mu`` as a northwest diagram. EXAMPLES:: @@ -1168,8 +1162,7 @@ def from_parallelogram_polyomino(self, p): def RotheDiagram(w): r""" - A constructor to build the Rothe diagram of a permutation w as an element - in the parent of :class:`NorthwestDiagrams`. + The Rothe diagram of a permutation ``w`` EXAMPLES:: @@ -1186,6 +1179,11 @@ def RotheDiagram(w): . . . . . O . . . . . . . . . . . . + The Rothe diagram is a northwest diagram:: + + sage: D.parent() + Combinatorial northwest diagrams + Currently, only elements of the parent :class:`sage.combinat.permutations.Permutations` are supported. In particular, elements of permutation groups are not supported:: From 578a87f6dd2151f1e03b8c11a1f5b8be33c2a3c6 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 17:59:34 -0500 Subject: [PATCH 206/414] < N_rows + 1 -> <= N_rows --- src/sage/combinat/diagram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 22171731c0b..fe45f5b5bd9 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -156,13 +156,13 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=True): N_cols = 0 if n_rows is not None: - if n_rows < N_rows + 1: + if n_rows <= N_rows: raise ValueError('n_rows is too small') self._n_rows = n_rows else: self._n_rows = N_rows + 1 if n_cols is not None: - if n_cols < N_cols + 1: + if n_cols <= N_cols: raise ValueError('n_cols is too small') self._n_cols = n_cols else: From 944fc764400aa553aab054cbd2b6ba8bbe685b75 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 18:09:32 -0500 Subject: [PATCH 207/414] Remove _hash_, __contains__, _repr_ overrides --- src/sage/combinat/diagram.py | 42 ++---------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index fe45f5b5bd9..d1ff6e1ce7c 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -171,45 +171,7 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=True): self._n_nonempty_rows = len(set(i for i, j in self._cells)) self._n_nonempty_cols = len(set(j for i, j in self._cells)) - ClonableArray.__init__(self, parent, cells, check) - - def _hash_(self): - r""" - TESTS:: - - sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]) - sage: hash(D) - 5125392318921082108 - """ - return hash(tuple(sorted(self._cells))) - - def __contains__(self, other): - r""" - EXAMPLES:: - - sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]) - sage: (1, 2) in D - False - sage: (0, 2) in D - True - sage: (2, 1) in D - False - sage: (3, 2) in D - True - """ - return other in self._cells - - def _repr_(self): - r""" - EXAMPLES:: - - sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,2),(0,3),(1,1),(3,2)]); D - [(0, 2), (0, 3), (1, 1), (3, 2)] - """ - return str(sorted(self._cells)) + ClonableArray.__init__(self, parent, sorted(cells), check) def pp(self): r""" @@ -474,7 +436,7 @@ def from_polyomino(self, p): """ if not p._dimension == 2: raise ValueError("Dimension of the polyomino must be 2") - cells = map(tuple, list(p)) + cells = list(map(tuple, p)) return self.element_class(self, cells) def from_composition(self, alpha): From 6ff9bc5c65db0f6a86a9fdb4e5ccdf18553dca38 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 19:28:55 -0500 Subject: [PATCH 208/414] Fix peelable tableaux documentation --- src/sage/combinat/diagram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index d1ff6e1ce7c..3722795a820 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -663,7 +663,7 @@ def peelable_tableaux(self): {[[3]]} Now we know all of the `D_5` peelables, so we can compute the `D_4` - peelables. + peelables:: sage: D4 = NorthwestDiagram([(0, 0), (2,0), (4, 0), (2, 2)]) sage: D4.pp() @@ -738,10 +738,10 @@ def peelable_tableaux(self): [[1, 1, 1, 1, 1, 3], [2, 2, 2, 3], [3, 3, 3], [5, 5, 5]], [[1, 1, 1, 1, 1, 3], [2, 2, 2, 3], [3, 3, 3, 5], [5, 5]]} - .. ALGORITHM:: + ALGORITHM: - This implementation uses the algorithm suggested in remark 25 - of [RS1995]_. + This implementation uses the algorithm suggested in Remark 25 + of [RS1995]_. """ # TODO: There is a condition on the first column (if the rows in Dhat # are a subset of the rows in the first column) which simplifies the From 7fc3ad0264efcecfb75d4cd5ad3354c00d549f60 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 19:29:24 -0500 Subject: [PATCH 209/414] Redo loop to avoid looping over self._cells twice --- src/sage/combinat/diagram.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 3722795a820..9c7c1eaa5e1 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -754,14 +754,17 @@ def peelable_tableaux(self): first_col = min(j for i, j in self._cells) - # TODO: The next two lines of code could be optimized by only - # looping over self._cells once (rather than two separate times) - # get the diagram without the first column - Dhat = NorthwestDiagram([c for c in self._cells if c[1] != first_col]) + dhat_cells = [] + new_vals_cells = [] + for i, j in self._cells: + if j != first_col: + dhat_cells.append((i, j)) + else: + new_vals_cells.append(i + 1) - # get the values from the first column - new_vals = sorted(i + 1 for i, j in self._cells if j == first_col) + new_vals = sorted(new_vals_cells) + Dhat = NorthwestDiagram(dhat_cells) k = self.n_cells() - Dhat.n_cells() peelables = set() From 5e9c78281da095ccafbf4926f81ea8c5ad39e929 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 19:31:53 -0500 Subject: [PATCH 210/414] Move zero/one inside check --- src/sage/combinat/diagram.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 9c7c1eaa5e1..393da822569 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -483,10 +483,9 @@ def from_zero_one_matrix(self, M, check=True): # check matrix is zero-one n_rows, n_cols = M.dimensions() - zero = M.base_ring().zero() - one = M.base_ring().one() - if check: + zero = M.base_ring().zero() + one = M.base_ring().one() for i in range(n_rows): for j in range(n_cols): if not (M[i,j] == zero or M[i,j] == one): From 90dd64c736bb63e3d715f1cd003893ac85b7e44b Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 19:33:26 -0500 Subject: [PATCH 211/414] Fix pyflakes issue --- src/sage/combinat/diagram.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 393da822569..80b292a0041 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -335,8 +335,6 @@ def check(self): ... ValueError: Diagrams must be indexed by non-negative integers """ - from sage.sets.non_negative_integers import NonNegativeIntegers - NN = NonNegativeIntegers() if not all(all(list(i in NN for i in c)) for c in self._cells): raise ValueError("Diagrams must be indexed by non-negative integers") From 5d1f75813e3ebd7d827a8ec0a418ac34ec7fe18d Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Mon, 12 Sep 2022 19:40:20 -0500 Subject: [PATCH 212/414] Fix pyflakes issue --- src/sage/combinat/diagram.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 80b292a0041..6838ebefa06 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -25,7 +25,6 @@ # **************************************************************************** from sage.categories.sets_cat import Sets -from sage.sets.non_negative_integers import NonNegativeIntegers as NN from sage.combinat.partition import Partition from sage.combinat.skew_partition import SkewPartition from sage.combinat.permutation import Permutations @@ -335,6 +334,8 @@ def check(self): ... ValueError: Diagrams must be indexed by non-negative integers """ + from sage.sets.non_negative_integers import NonNegativeIntegers + NN = NonNegativeIntegers() if not all(all(list(i in NN for i in c)) for c in self._cells): raise ValueError("Diagrams must be indexed by non-negative integers") @@ -1027,7 +1028,7 @@ def from_partition(self, mu): Combinatorial northwest diagrams This will print in English notation even if the notation is set to - French for the partition. + French for the partition:: sage: Partitions.options.convention="french" sage: mu.pp() From 4278363d322b82cb112b4db43de75087251250a7 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 13 Sep 2022 10:00:01 -0500 Subject: [PATCH 213/414] Add .from_* methods to __element_constructor__ --- src/sage/combinat/diagram.py | 65 ++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 6838ebefa06..d6f07a149da 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -25,16 +25,19 @@ # **************************************************************************** from sage.categories.sets_cat import Sets +from sage.combinat.composition import Composition from sage.combinat.partition import Partition -from sage.combinat.skew_partition import SkewPartition from sage.combinat.permutation import Permutations -from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.list_clone import ClonableArray -from sage.structure.parent import Parent from sage.combinat.tableau import Tableau +from sage.combinat.tiling import Polyomino +from sage.combinat.skew_partition import SkewPartition from sage.combinat.skew_tableau import SkewTableaux +from sage.matrix.matrix_dense import Matrix_dense +from sage.matrix.matrix_sparse import Matrix_sparse from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass - +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.list_clone import ClonableArray +from sage.structure.parent import Parent class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): r""" @@ -393,10 +396,41 @@ def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=True): . . . . . O + + sage: from sage.combinat.tiling import Polyomino + sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) + sage: Dgms(p).pp() + O . . + O O O + + sage: from sage.combinat.composition import Composition + sage: a = Composition([4,2,0,2,4]) + sage: Dgms(a).pp() + O O O O + O O . . + . . . . + O O . . + O O O O + + sage: M = Matrix([[1,1,1,1],[1,1,0,0],[0,0,0,0],[1,1,0,0],[1,1,1,1]]) + sage: Dgms(M).pp() + O O O O + O O . . + . . . . + O O . . + O O O O + TESTS:: sage: TestSuite(Dgms).run() """ + if isinstance(cells, Polyomino): + return self.from_polyomino(cells) + if isinstance(cells, Composition): + return self.from_composition(cells) + if isinstance(cells, (Matrix_dense, Matrix_sparse)): + return self.from_zero_one_matrix(cells) + return self.element_class(self, cells, n_rows, n_cols, check) def _an_element_(self): @@ -424,9 +458,18 @@ def from_polyomino(self, p): sage: from sage.combinat.tiling import Polyomino sage: p = Polyomino([(0,0),(1,0),(1,1),(1,2)]) sage: from sage.combinat.diagram import Diagrams + sage: Diagrams()(p).pp() + O . . + O O O + + We can also call this method directly:: + sage: Diagrams().from_polyomino(p).pp() O . . O O O + + The method only works for 2d `Polyomino`s:: + sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue') sage: Diagrams().from_polyomino(p) Traceback (most recent call last): @@ -446,6 +489,13 @@ def from_composition(self, alpha): sage: alpha = Composition([3,0,2,1,4,4]) sage: from sage.combinat.diagram import Diagrams + sage: Diagrams()(alpha).pp() + O O O . + . . . . + O O . . + O . . . + O O O O + O O O O sage: Diagrams().from_composition(alpha).pp() O O O . . . . . @@ -468,12 +518,15 @@ def from_zero_one_matrix(self, M, check=True): sage: M = matrix([[1,0,1,1],[0,1,1,0]]) sage: from sage.combinat.diagram import Diagrams + sage: Diagrams()(M).pp() + O . O O + . O O . sage: Diagrams().from_zero_one_matrix(M).pp() O . O O . O O . sage: M = matrix([[1, 0, 0], [1, 0, 0], [0, 0, 0]]) - sage: Diagrams().from_zero_one_matrix(M).pp() + sage: Diagrams()(M).pp() O . . O . . . . . From d7c74f465a15d83f505dc8eda1bb972c873b011d Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 17 Sep 2022 12:18:59 -0500 Subject: [PATCH 214/414] Add diagram iterator --- src/sage/combinat/diagram.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index d6f07a149da..71239537797 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -25,6 +25,7 @@ # **************************************************************************** from sage.categories.sets_cat import Sets +from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.combinat.composition import Composition from sage.combinat.partition import Partition from sage.combinat.permutation import Permutations @@ -360,7 +361,7 @@ class Diagrams(UniqueRepresentation, Parent): Combinatorial diagrams """ - def __init__(self): + def __init__(self, category=None): r""" EXAMPLES:: @@ -373,7 +374,38 @@ def __init__(self): sage: TestSuite(Dgms).run() """ - Parent.__init__(self, category=Sets()) + Parent.__init__(self, category=InfiniteEnumeratedSets().or_subcategory(category)) + + def __iter__(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagrams + sage: I = iter(Diagrams()) + sage: for i in range(3): + ....: print(next(I)) + [] + [(0, 0)] + [(0, 1)] + sage: next(I).parent() + Combinatorial diagrams + + sage: del(I) + sage: D = Diagrams() + sage: for d in D: + ....: if len(d) >= 3: + ....: break + sage: d + [(0, 0), (0, 1), (0, 2)] + """ + from sage.sets.non_negative_integers import NonNegativeIntegers + from sage.misc.mrange import cartesian_product_iterator + from sage.misc.misc import subsets + NN = NonNegativeIntegers() + P = cartesian_product_iterator([NN, NN]) + X = subsets(P) + while True: + yield self.element_class(self, next(X)) def _repr_(self): r""" From ae1d71a5eb1e14b71bfb0e6a8a6c87342e7e4fdf Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 17 Sep 2022 13:48:26 -0500 Subject: [PATCH 215/414] Add tests and add a catch for subclasses --- src/sage/combinat/diagram.py | 60 ++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 71239537797..a904618dffe 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -382,30 +382,64 @@ def __iter__(self): sage: from sage.combinat.diagram import Diagrams sage: I = iter(Diagrams()) - sage: for i in range(3): + sage: for i in range(10): ....: print(next(I)) [] [(0, 0)] + [(1, 0)] + [(0, 0), (1, 0)] [(0, 1)] + [(0, 0), (0, 1)] + [(0, 1), (1, 0)] + [(0, 0), (0, 1), (1, 0)] + [(2, 0)] + [(0, 0), (2, 0)] sage: next(I).parent() Combinatorial diagrams - sage: del(I) - sage: D = Diagrams() - sage: for d in D: - ....: if len(d) >= 3: - ....: break - sage: d - [(0, 0), (0, 1), (0, 2)] + sage: from sage.combinat.diagram import NorthwestDiagrams + sage: I = iter(NorthwestDiagrams()) + sage: for i in range(20): + ....: print(next(I)) + [] + [(0, 0)] + [(1, 0)] + [(0, 0), (1, 0)] + [(0, 1)] + [(0, 0), (0, 1)] + [(0, 0), (0, 1), (1, 0)] + [(2, 0)] + [(0, 0), (2, 0)] + [(1, 0), (2, 0)] + [(0, 0), (1, 0), (2, 0)] + [(0, 0), (0, 1), (2, 0)] + [(0, 0), (0, 1), (1, 0), (2, 0)] + [(1, 1)] + [(0, 0), (1, 1)] + [(1, 0), (1, 1)] + [(0, 0), (1, 0), (1, 1)] + [(0, 1), (1, 1)] + [(0, 0), (0, 1), (1, 1)] + [(0, 0), (0, 1), (1, 0), (1, 1)] """ - from sage.sets.non_negative_integers import NonNegativeIntegers - from sage.misc.mrange import cartesian_product_iterator + from sage.sets.positive_integers import PositiveIntegers + from sage.all import cartesian_product from sage.misc.misc import subsets - NN = NonNegativeIntegers() - P = cartesian_product_iterator([NN, NN]) + # the product of positive integers automatically implements an + # an enumeration which allows us to get out of the first column + PP = PositiveIntegers() + P = cartesian_product([PP, PP]) X = subsets(P) while True: - yield self.element_class(self, next(X)) + # we want to allow cells in the index-0 row but we + # dont want all of them to be in the index-0 row + cells = next(X) + try: + yield self.element_class(self, tuple((i-1, j-1) for i,j in cells)) + except ValueError: + # if cells causes the .check method of a + # subclass to fail, just go to the next one + pass def _repr_(self): r""" From 217c2039c96fcf230d361572bfa1f24361d23679 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Tue, 20 Sep 2022 07:42:56 -0500 Subject: [PATCH 216/414] PositiveIntegers -> NonNegativeIntegers --- src/sage/combinat/diagram.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index a904618dffe..324eb96312e 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -422,20 +422,18 @@ def __iter__(self): [(0, 0), (0, 1), (1, 1)] [(0, 0), (0, 1), (1, 0), (1, 1)] """ - from sage.sets.positive_integers import PositiveIntegers - from sage.all import cartesian_product + from sage.sets.non_negative_integers import NonNegativeIntegers + from sage.categories.cartesian_product import cartesian_product from sage.misc.misc import subsets # the product of positive integers automatically implements an # an enumeration which allows us to get out of the first column - PP = PositiveIntegers() - P = cartesian_product([PP, PP]) - X = subsets(P) + N = NonNegativeIntegers() + NxN = cartesian_product([N, N]) + X = subsets(NxN) while True: - # we want to allow cells in the index-0 row but we - # dont want all of them to be in the index-0 row cells = next(X) try: - yield self.element_class(self, tuple((i-1, j-1) for i,j in cells)) + yield self.element_class(self, tuple((i, j) for i,j in cells)) except ValueError: # if cells causes the .check method of a # subclass to fail, just go to the next one From 192d2e03ed61927fb540b3d704c62afe2de64907 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Thu, 22 Sep 2022 21:51:06 -0500 Subject: [PATCH 217/414] Add unicode/ascii art --- src/sage/combinat/diagram.py | 79 +++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 324eb96312e..bb49857afb5 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -199,14 +199,89 @@ def pp(self): . . . . . . . . . . . . """ + return self._pretty_print() + + def _ascii_art_(self): + r""" + Return a visualization of the diagram. + + Cells which are present in the + diagram are filled with a ``O``. Cells which are not present in the + diagram are filled with a ``.``. + + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: ascii_art(Diagram([(0,0), (0,3), (2,2), (2,4)])) + O . . O . + . . . . . + . . O . O + sage: ascii_art(Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6)) + O . . O . . + . . . . . . + . . O . O . + . . . . . . + . . . . . . + . . . . . . + """ + return self._pretty_print() + + def _unicode_art_(self): + r""" + Return a unicode visualization of the diagram. + + Cells which are present in the + diagram are filled with a crossed box. Cells which are not present in the + diagram are filled with an empty box. + + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: unicode_art(Diagram([(0,0), (0,3), (2,2), (2,4)])) + ☒☐☐☒☐ + ☐☐☐☐☐ + ☐☐☒☐☒ + sage: unicode_art(Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6)) + ☒☐☐☒☐☐ + ☐☐☐☐☐☐ + ☐☐☒☐☒☐ + ☐☐☐☐☐☐ + ☐☐☐☐☐☐ + ☐☐☐☐☐☐ + """ + return self._pretty_print('☒', '☐') + + def _pretty_print(self, cell='O ', empty='. '): + r""" + Return a visualization of the diagram. + + Cells which are present in the + diagram are filled with ``cell``. Cells which are not present in the + diagram are filled with ``empty``. + + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: Diagram([(0,0), (0,3), (2,2), (2,4)])._pretty_print('x ','. ') + x . . x . + . . . . . + . . x . x + sage: Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6)._pretty_print('x ','. ') + x . . x . . + . . . . . . + . . x . x . + . . . . . . + . . . . . . + . . . . . . + """ output_str = '' for i in range(self._n_rows): for j in range(self._n_cols): if (i, j) in self: - output_str += 'O ' + output_str += cell else: - output_str += '. ' + output_str += empty output_str += '\n' print(output_str, end='') # don't double up on `\n`'ss From a4a94749cb08ea350fb9d8ee1b630b0f28bf6b2b Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 23 Sep 2022 07:47:42 -0500 Subject: [PATCH 218/414] Add draft of _latex_ and fix pretty_print/ascii_art string things --- src/sage/combinat/diagram.py | 47 +++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index bb49857afb5..5348a9c100c 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -199,7 +199,7 @@ def pp(self): . . . . . . . . . . . . """ - return self._pretty_print() + print(self._pretty_print(), end='') def _ascii_art_(self): r""" @@ -224,7 +224,7 @@ def _ascii_art_(self): . . . . . . . . . . . . """ - return self._pretty_print() + return ascii_art(self._pretty_print()) def _unicode_art_(self): r""" @@ -249,7 +249,7 @@ def _unicode_art_(self): ☐☐☐☐☐☐ ☐☐☐☐☐☐ """ - return self._pretty_print('☒', '☐') + return unicode_art(self._pretty_print('☒', '☐')) def _pretty_print(self, cell='O ', empty='. '): r""" @@ -284,7 +284,46 @@ def _pretty_print(self, cell='O ', empty='. '): output_str += empty output_str += '\n' - print(output_str, end='') # don't double up on `\n`'ss + return output_str + + def _latex_(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.diagram import Diagram + sage: latex(Diagram([])) + sage: latex(Diagram([(0,0), (0,3), (2,2), (2,4)])) + """ + if not self.cells(): + return "{\\emptyset}" + from string import Template + lr = Template(r'\def\lr#1{\multicolumn{1}{$|@{\hspace{.6ex}}c@{\hspace{.6ex}}$|}{\raisebox{-.3ex}{$$#1$$}}}') + + array = [] + for i in range(self._n_rows): + row = [] + for j in range(self._n_cols): + row.append("\\phantom{x}" if (i, j) in self else None) + array.append(row) + + def end_line(r): + # give the line ending to row ``r`` + if r == 0: + return "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[0]) if j != None) + elif r == len(array): + return "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) + else: + out = "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) + out += "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r]) if j != None) + return out + + # now we draw the arrayarray + tex=r'\raisebox{-.6ex}{$\begin{array}[b]{*{%s}{p{0.6ex}}}'%(max(map(len,array))) + tex+=end_line(0)+'\n' + for r in range(len(array)): + tex+='&'.join('' if c is None else r'\lr{%s}'%(c,) for c in array[r]) + tex += end_line(r+1)+'\n' + return tex+r'\end{array}$}' def number_of_rows(self): r""" From 740651ff031dcb1bb81c6eb67e61bfa32ea78846 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Fri, 23 Sep 2022 14:09:28 -0500 Subject: [PATCH 219/414] Fix latex and unicode/ascii art --- src/sage/combinat/diagram.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 5348a9c100c..8662fb0a8e1 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -224,6 +224,7 @@ def _ascii_art_(self): . . . . . . . . . . . . """ + from sage.typeset.ascii_art import ascii_art return ascii_art(self._pretty_print()) def _unicode_art_(self): @@ -249,6 +250,7 @@ def _unicode_art_(self): ☐☐☐☐☐☐ ☐☐☐☐☐☐ """ + from sage.typeset.unicode_art import unicode_art return unicode_art(self._pretty_print('☒', '☐')) def _pretty_print(self, cell='O ', empty='. '): @@ -263,16 +265,9 @@ def _pretty_print(self, cell='O ', empty='. '): sage: from sage.combinat.diagram import Diagram sage: Diagram([(0,0), (0,3), (2,2), (2,4)])._pretty_print('x ','. ') - x . . x . - . . . . . - . . x . x + 'x . . x . \n. . . . . \n. . x . x \n' sage: Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6)._pretty_print('x ','. ') - x . . x . . - . . . . . . - . . x . x . - . . . . . . - . . . . . . - . . . . . . + 'x . . x . . \n. . . . . . \n. . x . x . \n. . . . . . \n. . . . . . \n. . . . . . \n' """ output_str = '' @@ -292,12 +287,21 @@ def _latex_(self): sage: from sage.combinat.diagram import Diagram sage: latex(Diagram([])) + {\emptyset} sage: latex(Diagram([(0,0), (0,3), (2,2), (2,4)])) + {\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{5}{p{0.6ex}}}\cline{1-1}\cline{4-4} + \lr{\phantom{x}}&&&\lr{\phantom{x}}&\\\cline{1-1}\cline{4-4} + &&&&\\\cline{3-3}\cline{5-5} + &&\lr{\phantom{x}}&&\lr{\phantom{x}}\\\cline{3-3}\cline{5-5} + \end{array}$} + } + """ if not self.cells(): return "{\\emptyset}" - from string import Template - lr = Template(r'\def\lr#1{\multicolumn{1}{$|@{\hspace{.6ex}}c@{\hspace{.6ex}}$|}{\raisebox{-.3ex}{$$#1$$}}}') + + lr = r'\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}}' array = [] for i in range(self._n_rows): @@ -305,25 +309,24 @@ def _latex_(self): for j in range(self._n_cols): row.append("\\phantom{x}" if (i, j) in self else None) array.append(row) - + def end_line(r): # give the line ending to row ``r`` if r == 0: return "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[0]) if j != None) elif r == len(array): - return "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) + return r"\\" + "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) else: - out = "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) + out = r"\\" + "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) out += "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r]) if j != None) return out - # now we draw the arrayarray tex=r'\raisebox{-.6ex}{$\begin{array}[b]{*{%s}{p{0.6ex}}}'%(max(map(len,array))) tex+=end_line(0)+'\n' for r in range(len(array)): tex+='&'.join('' if c is None else r'\lr{%s}'%(c,) for c in array[r]) tex += end_line(r+1)+'\n' - return tex+r'\end{array}$}' + return '{%s\n%s\n}' % (lr, tex+r'\end{array}$}') def number_of_rows(self): r""" From b3a378394cca4b7c8e248695966cf51a18d65c2d Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 24 Sep 2022 08:28:59 -0500 Subject: [PATCH 220/414] !=None -> is not None --- src/sage/combinat/diagram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index 8662fb0a8e1..ba84ef2dc08 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -313,12 +313,12 @@ def _latex_(self): def end_line(r): # give the line ending to row ``r`` if r == 0: - return "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[0]) if j != None) + return "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[0]) if j is not None) elif r == len(array): - return r"\\" + "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) + return r"\\" + "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j is not None) else: - out = r"\\" + "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j != None) - out += "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r]) if j != None) + out = r"\\" + "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r-1]) if j is not None) + out += "".join(r'\cline{%s-%s}'%(i+1, i+1) for i,j in enumerate(array[r]) if j is not None) return out tex=r'\raisebox{-.6ex}{$\begin{array}[b]{*{%s}{p{0.6ex}}}'%(max(map(len,array))) From e6c4cae0902abbdef92b68badad66c8833021eb3 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 12 Oct 2022 16:42:58 +0200 Subject: [PATCH 221/414] make Stream_uninitialized always dense to avoid maximal recursion error --- src/sage/data_structures/stream.py | 40 ++++++++---------------------- src/sage/rings/lazy_series.py | 2 ++ src/sage/rings/lazy_series_ring.py | 8 +++--- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index 92aa652403a..e9587b3d009 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -955,7 +955,7 @@ class Stream_function(Stream_inexact): [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] sage: f = Stream_function(lambda n: n, True, 0) - sage: f.get_coefficient(4) + sage: f[4] 4 """ def __init__(self, function, is_sparse, approximate_order, true_order=False): @@ -979,58 +979,38 @@ class Stream_uninitialized(Stream_inexact): INPUT: - - ``is_sparse`` -- boolean; which specifies whether the stream is sparse - ``approximate_order`` -- integer; a lower bound for the order of the stream + Instances of this class are always dense. + EXAMPLES:: sage: from sage.data_structures.stream import Stream_uninitialized sage: from sage.data_structures.stream import Stream_exact sage: one = Stream_exact([1]) - sage: C = Stream_uninitialized(True, 0) + sage: C = Stream_uninitialized(0) sage: C._target sage: C._target = one - sage: C.get_coefficient(4) + sage: C[4] 0 """ - def __init__(self, is_sparse, approximate_order, true_order=False): + def __init__(self, approximate_order, true_order=False): """ Initialize ``self``. TESTS:: sage: from sage.data_structures.stream import Stream_uninitialized - sage: C = Stream_uninitialized(False, 0) + sage: C = Stream_uninitialized(0) sage: TestSuite(C).run(skip="_test_pickling") """ self._target = None if approximate_order is None: raise ValueError("the valuation must be specified for undefined series") - super().__init__(is_sparse, true_order) + super().__init__(False, true_order) self._approximate_order = approximate_order - def get_coefficient(self, n): - """ - Return the ``n``-th coefficient of ``self``. - - INPUT: - - - ``n`` -- integer; the degree for the coefficient - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_uninitialized - sage: from sage.data_structures.stream import Stream_exact - sage: one = Stream_exact([1]) - sage: C = Stream_uninitialized(True, 0) - sage: C._target - sage: C._target = one - sage: C.get_coefficient(0) - 1 - """ - return self._target[n] - def iterate_coefficients(self): """ A generator for the coefficients of ``self``. @@ -1040,7 +1020,7 @@ def iterate_coefficients(self): sage: from sage.data_structures.stream import Stream_uninitialized sage: from sage.data_structures.stream import Stream_exact sage: z = Stream_exact([1], order=1) - sage: C = Stream_uninitialized(True, 0) + sage: C = Stream_uninitialized(0) sage: C._target sage: C._target = z sage: n = C.iterate_coefficients() @@ -2520,7 +2500,7 @@ def _approximate_order(self): """ try: return -self._series.order() - except RecursionError: + except (ValueError, RecursionError): raise ValueError("inverse does not exist") @lazy_attribute diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 5b573ef5b19..c302de2a016 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -978,6 +978,8 @@ def define(self, s): sage: C.define(1 + z*C^2) sage: C 1 + z + 2*z^2 + 5*z^3 + 14*z^4 + 42*z^5 + 132*z^6 + O(z^7) + sage: binomial(2000, 1000) / C[1000] + 1001 The Catalan numbers but with a valuation 1:: diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 27e0f963625..a513d903674 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -433,7 +433,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No if x is None and coefficients is None: if valuation is None: raise ValueError("the valuation must be specified") - return self.element_class(self, Stream_uninitialized(self._sparse, valuation)) + return self.element_class(self, Stream_uninitialized(valuation)) # WARNING: if x is not explicitly specified as None, it is # set to 0 by Parent.__call__ @@ -606,7 +606,7 @@ def undefined(self, valuation=None): """ if valuation is None: valuation = self._minimal_valuation - coeff_stream = Stream_uninitialized(self._sparse, valuation) + coeff_stream = Stream_uninitialized(valuation) return self.element_class(self, coeff_stream) unknown = undefined @@ -1817,7 +1817,7 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No BR = self.base_ring() if x is None: assert degree is None - coeff_stream = Stream_uninitialized(self._sparse, valuation) + coeff_stream = Stream_uninitialized(valuation) return self.element_class(self, coeff_stream) try: @@ -2285,7 +2285,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No R = self._laurent_poly_ring if x is None: assert degree is None - coeff_stream = Stream_uninitialized(self._sparse, valuation) + coeff_stream = Stream_uninitialized(valuation) return self.element_class(self, coeff_stream) try: # Try to build stuff using the polynomial ring constructor From e09725c4973792a294d84923746b3097befe5770 Mon Sep 17 00:00:00 2001 From: Johann Birnick Date: Wed, 12 Oct 2022 16:27:09 +0100 Subject: [PATCH 222/414] Added multivariate_interpolation for multivariate polynomial rings. --- .../polynomial/multi_polynomial_ring_base.pyx | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index 02eeb51bacf..f0e2ac0d6ca 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -1,6 +1,12 @@ r""" Base class for multivariate polynomial rings """ +import itertools +import warnings +from collections.abc import Iterable +from sage.matrix.constructor import matrix +from sage.modules.free_module_element import vector + import sage.misc.latex from sage.misc.cachefunc import cached_method from sage.misc.misc_c import prod @@ -341,6 +347,109 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): """ return self.remove_var(x)[str(x)] + def multivariate_interpolation(self, bound, *args): + """ + Create a polynomial with specified evaluations. + + CALL FORMATS: + + This function can be called in two ways: + + 1. multivariate_interpolation(bound, points, values) + + 2. multivariate_interpolation(bound, function) + + INPUT: + + * "bound" -- either an integer bounding the total degree or a list/tuple of + integers bounding the degree of the variables + + * "points" -- a list/tuple containing the evaluation points + + * "values" -- a list/tuple containing the desired values at "points" + + * "function" -- a evaluable function in n variables, where n is the number + of variables of the polynomial ring + + OUTPUT: + + 1. A polynomial respecting the bounds and having "values" as values when + evaluated at "points". + + 2. A polynomial respecting the bounds and having the same values as + "function" at exactly so many points so that the polynomial is unique. + + EXAMPLES:: + + sage: def F(a,b,c): + ....: return a^3*b + b + c^2 + 25 + ....: + sage: R. = PolynomialRing(QQ) + sage: R.multivariate_interpolation([3,1,2], F) + x^3*y + z^2 + y + 25 + """ + # get ring and number of variables + R = self.base_ring() + n = self.ngens() + + # we only run the algorithm over fields + if not R.is_field(): + raise TypeError(f'The base ring {R} is not a field.') + + # helper function to sample "num_samples" elements from R + def sample_points(num_samples): + try: + samples = list(itertools.islice(R, num_samples)) + if len(samples) < num_samples: + raise ValueError(f'Could not sample {num_samples} different elements of {R}.') + except NotImplementedError: + if R.characteristic() == 0 or R.characteristic() >= num_samples: + samples = [R(k) for k in range(num_samples)] + else: + raise NotImplementedError(f'Could not sample {num_samples} different elements of {R}.') + + return samples + + # set points and values + if len(args) == 2: + points, values = args + else: + F, = args + + if isinstance(bound, Iterable): + R_points = sample_points(max(bound) + 1) + points = list(itertools.product(*[R_points[:bound[i] + 1] for i in range(n)])) + else: + points = list(itertools.combinations_with_replacement(sample_points(bound + 1), n)) + + values = [F(*x) for x in points] + + # find all possibly appearing exponents + if isinstance(bound, Iterable): + exponents_space = list(itertools.product(*(range(bound[i] + 1) for i in range(n)))) + else: + exponents_space = [] + for entry in itertools.combinations_with_replacement(range(bound + 1), n): + exponents_space.append([entry[0]] + [entry[i] - entry[i - 1] for i in range(1, n)]) + + # build matrix + M = matrix.zero(R, 0, len(points)) + for exponents in exponents_space: + M = M.stack(vector(R, [self.monomial(*exponents)(*x) for x in points])) + + # solve for coefficients and construct polynomial + try: + coeff = M.solve_left(vector(R, values)) + except ValueError: + raise ValueError('Could not find a solution.') + solution = sum(coeff[i] * self.monomial(*exponents_space[i]) for i in range(len(exponents_space))) + + # warn the user if the solution is not unique + if M.left_kernel().dimension() > 0: + warnings.warn('The solution is not unique.') + + return solution + def _coerce_map_from_base_ring(self): """ Return a coercion map from the base ring of ``self``. From 1ff619c2c10ea50ae2b58bac0a2ea50c9db7e991 Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 12 Oct 2022 22:41:36 -0500 Subject: [PATCH 223/414] Remove SSF --- src/sage/combinat/permutation.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 48b188aacc7..d8fb066a72e 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -220,8 +220,7 @@ - Travis Scrimshaw (2014-02-05): Made :class:`StandardPermutations_n` a finite Weyl group to make it more uniform with :class:`SymmetricGroup`. Added ability to compute the conjugacy classes. -- Trevor K. Karn (2022-08-05): Add :meth:`Permutation.n_reduced_words` and - :meth:`Permutation.stanley_symmetric_function`. +- Trevor K. Karn (2022-08-05): Add :meth:`Permutation.n_reduced_words` - Amrutha P, Shriya M, Divya Aggarwal (2022-08-16): Added Multimajor Index. Classes and methods @@ -3028,26 +3027,6 @@ def number_of_reduced_words(self): return sum(map(_tableau_contribution, Tx)) - def stanley_symmetric_function(self): - r""" - Return the Stanley symmetric function associated to ``self``. - - EXAMPLES:: - - sage: p = Permutation([4,5,2,3,1]) - sage: p.stanley_symmetric_function() - 56*m[1, 1, 1, 1, 1, 1, 1, 1] + 30*m[2, 1, 1, 1, 1, 1, 1] - + 16*m[2, 2, 1, 1, 1, 1] + 9*m[2, 2, 2, 1, 1] + 6*m[2, 2, 2, 2] - + 10*m[3, 1, 1, 1, 1, 1] + 5*m[3, 2, 1, 1, 1] + 3*m[3, 2, 2, 1] - + m[3, 3, 1, 1] + m[3, 3, 2] + 2*m[4, 1, 1, 1, 1] + m[4, 2, 1, 1] - + m[4, 2, 2] - """ - from sage.combinat.sf.sf import SymmetricFunctions - from sage.rings.rational_field import QQ - s = SymmetricFunctions(QQ).s() - m = SymmetricFunctions(QQ).m() - return m(sum(s[T.shape()] for T in self.rothe_diagram().peelable_tableaux())) - ################ # Fixed Points # ################ From 965b28499a900390601bce60fc8d1e3f9ceff9ed Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Wed, 12 Oct 2022 23:12:06 -0500 Subject: [PATCH 224/414] Fix w-inverse --- src/sage/combinat/diagram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index ba84ef2dc08..b6982155197 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -1410,8 +1410,8 @@ def RotheDiagram(w): raise ValueError('w must be a Permutation') N = w.size() - + winv = w.inverse() cells = [c for c in cartesian_product_iterator((range(N), range(N))) - if c[0]+1 < w.inverse()(c[1]+1) and c[1]+1 < w(c[0]+1)] + if c[0]+1 < winv(c[1]+1) and c[1]+1 < w(c[0]+1)] return NorthwestDiagram(cells, n_rows=N, n_cols=N, check=False) From de424bd78aab5b7e151316644ad460b39261734f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 13 Oct 2022 08:50:39 +0200 Subject: [PATCH 225/414] make internal rings sparse or dense if the lazy series ring is sparse or dense --- src/sage/rings/lazy_series_ring.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index a513d903674..ca4cb8aa8ff 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1197,8 +1197,7 @@ def __init__(self, base_ring, names, sparse=True, category=None): raise ValueError("only univariate lazy Laurent series are implemented") self._arity = 1 self._minimal_valuation = None - # We always use the dense because our CS_exact is implemented densely - self._laurent_poly_ring = LaurentPolynomialRing(base_ring, names) + self._laurent_poly_ring = LaurentPolynomialRing(base_ring, names, sparse=sparse) self._internal_poly_ring = self._laurent_poly_ring category = Algebras(base_ring.category()) @@ -1563,12 +1562,13 @@ def __init__(self, base_ring, names, sparse=True, category=None): """ self._sparse = sparse self._minimal_valuation = 0 - self._laurent_poly_ring = PolynomialRing(base_ring, names) self._arity = len(names) if self._arity == 1: + self._laurent_poly_ring = PolynomialRing(base_ring, names, sparse=sparse) self._internal_poly_ring = self._laurent_poly_ring else: - self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE") + self._laurent_poly_ring = PolynomialRing(base_ring, names) + self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE", sparse=sparse) category = Algebras(base_ring.category()) mixin_gcd = False if self._arity == 1: @@ -2160,7 +2160,7 @@ def __init__(self, basis, sparse=True, category=None): from sage.algebras.free_algebra import FreeAlgebra self._internal_poly_ring = FreeAlgebra(self._laurent_poly_ring, 1, "DUMMY_VARIABLE") else: - self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE") + self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE", sparse=sparse) def _repr_(self): """ @@ -2560,7 +2560,7 @@ def __init__(self, base_ring, names, sparse=True, category=None): self._minimal_valuation = 1 self._arity = 1 self._laurent_poly_ring = SR # TODO: it would be good to have something better than the symbolic ring - self._internal_poly_ring = PolynomialRing(base_ring, names, sparse=True) + self._internal_poly_ring = PolynomialRing(base_ring, names, sparse=sparse) category = Algebras(base_ring.category()) if base_ring in IntegralDomains(): From 58de6133486d60c05d8c474faf6f376ed5bc890b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 13 Oct 2022 11:55:10 +0200 Subject: [PATCH 226/414] fix pickling for FrobeniusEndomorphism_generic --- src/sage/rings/morphism.pyx | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index 96a55bffd69..cc6b6e9efc8 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -2939,6 +2939,44 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): self._q = self._p ** self._power RingHomomorphism.__init__(self, Hom(domain, domain)) + cdef _update_slots(self, dict _slots): + """ + Helper for copying and pickling. + + EXAMPLES:: + + sage: K = Frac(GF(5)['T']) + sage: phi = K.frobenius_endomorphism() + sage: psi = copy(phi) + sage: phi == psi + True + """ + self._p = _slots['prime'] + self._power = _slots['power'] + self._q = self._p ** self._power + RingHomomorphism._update_slots(self, _slots) + + cdef dict _extra_slots(self): + """ + Helper for copying and pickling. + + EXAMPLES:: + + sage: K = Frac(GF(25)['T']) + sage: phi = K.frobenius_endomorphism(2) + sage: phi + Frobenius endomorphism x |--> x^(5^2) of Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 + + sage: psi = loads(dumps(phi)); psi + Frobenius endomorphism x |--> x^(5^2) of Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 + sage: phi == psi + True + """ + slots = RingHomomorphism._extra_slots(self) + slots['prime'] = self._p + slots['power'] = self._power + return slots + def _repr_(self): """ Return a string representation of this endomorphism. From 5e29ad644717a5c3ef8af53a8fbda8e1bdfdf4a8 Mon Sep 17 00:00:00 2001 From: Johann Birnick Date: Thu, 13 Oct 2022 11:45:19 +0100 Subject: [PATCH 227/414] Improved documentation. --- .../polynomial/multi_polynomial_ring_base.pyx | 63 ++++++++++++++++--- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index f0e2ac0d6ca..d5fbf8059ac 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -348,7 +348,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): return self.remove_var(x)[str(x)] def multivariate_interpolation(self, bound, *args): - """ + r""" Create a polynomial with specified evaluations. CALL FORMATS: @@ -361,32 +361,77 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): INPUT: - * "bound" -- either an integer bounding the total degree or a list/tuple of - integers bounding the degree of the variables + * "bound" -- either an integer bounding the total degree or a list/tuple of integers bounding the degree of the variables * "points" -- a list/tuple containing the evaluation points * "values" -- a list/tuple containing the desired values at "points" - * "function" -- a evaluable function in n variables, where n is the number - of variables of the polynomial ring + * "function" -- a evaluable function in n variables, where n is the number of variables of the polynomial ring OUTPUT: - 1. A polynomial respecting the bounds and having "values" as values when - evaluated at "points". + 1. A polynomial respecting the bounds and having "values" as values when evaluated at "points". - 2. A polynomial respecting the bounds and having the same values as - "function" at exactly so many points so that the polynomial is unique. + 2. A polynomial respecting the bounds and having the same values as "function" at exactly so many points so that the polynomial is unique. EXAMPLES:: + sage: def F(a,b,c): + ....: return a^3*b + b + c^2 + 25 + ....: + sage: R. = PolynomialRing(QQ) + sage: R.multivariate_interpolation(4, F) + x^3*y + z^2 + y + 25 + + sage: def F(a,b,c): ....: return a^3*b + b + c^2 + 25 ....: sage: R. = PolynomialRing(QQ) sage: R.multivariate_interpolation([3,1,2], F) x^3*y + z^2 + y + 25 + + + sage: def F(a,b,c): + ....: return a^3*b + b + c^2 + 25 + ....: + sage: R. = PolynomialRing(QQ) + sage: points = [(5,1,1),(7,2,2),(8,5,-1),(2,5,3),(1,4,0),(5,9,0), + ....: (2,7,0),(1,10,13),(0,0,1),(-1,1,0),(2,5,3),(1,1,1),(7,4,11), + ....: (12,1,9),(1,1,3),(4,-1,2),(0,1,5),(5,1,3),(3,1,-2),(2,11,3), + ....: (4,12,19),(3,1,1),(5,2,-3),(12,1,1),(2,3,4)] + sage: R.multivariate_interpolation([3,1,2], points, [F(*x) for x in points]) + x^3*y + z^2 + y + 25 + + ALGORITHM: + + Solves a linear system of equations with the linear algebra module. + If the points are not specified, it samples exactly as many points as needed for a unique solution. + + NOTE: + + It will only run if the base ring is a field, even though it might work otherwise as well. + If your base ring is an integral domain, let it run over the fraction field. + + WARNING:: + + If you don't provide point/value pairs but just a function, it + will only use as many points as needed for a unique solution with + the given bounds. In particular it will *not* notice or check + whether the result yields the correct evaluation for other points + as well. So if you give wrong bounds, you will get a wrong answer + without a warning. + + sage: def F(a,b,c): + ....: return a^3*b + b + c^2 + 25 + ....: + sage: R. = PolynomialRing(QQ) + sage: R.multivariate_interpolation(3,F) + 1/2*x^3 + x*y + z^2 - 1/2*x + y + 25 + + SEEALSO: + :meth:`lagrange_polynomial` """ # get ring and number of variables R = self.base_ring() From 5020b9decfe8a8f375102c254c28cb51ce3c7a12 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 13 Oct 2022 13:10:00 +0200 Subject: [PATCH 228/414] adapt exp and log to new sparsity --- src/sage/data_structures/stream.py | 6 ++++++ src/sage/rings/lazy_series.py | 17 ++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index e9587b3d009..f8f6dc6a186 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -984,6 +984,11 @@ class Stream_uninitialized(Stream_inexact): Instances of this class are always dense. + .. TODO:: + + shouldn't instances of this class share the cache with its + ``_target``? + EXAMPLES:: sage: from sage.data_structures.stream import Stream_uninitialized @@ -994,6 +999,7 @@ class Stream_uninitialized(Stream_inexact): sage: C._target = one sage: C[4] 0 + """ def __init__(self, approximate_order, true_order=False): """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 2b0c9adc937..902d57d5bab 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -1213,12 +1213,13 @@ def define(self, s): if not isinstance(s, LazyModuleElement): s = self.parent()(s) + coeff_stream = s._coeff_stream # Special case when it has a trivial definition - if isinstance(s._coeff_stream, (Stream_zero, Stream_exact)): - self._coeff_stream = s._coeff_stream + if isinstance(coeff_stream, (Stream_zero, Stream_exact)): + self._coeff_stream = coeff_stream return - self._coeff_stream._target = s._coeff_stream + self._coeff_stream._target = coeff_stream # an alias for compatibility with padics set = define @@ -3137,12 +3138,13 @@ def exp(self): raise ValueError("can only compose with a positive valuation series") # WARNING: d_self need not be a proper element of P, e.g. for # multivariate power series + # We make the streams dense, because all coefficients have to be computed anyway d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], - P.is_sparse(), 0) + False, 0) f = P.undefined(valuation=0) - d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream) + d_self_f = Stream_cauchy_mul(d_self, f._coeff_stream, False) int_d_self_f = Stream_function(lambda n: d_self_f[n-1] / R(n) if n else R.one(), - P.is_sparse(), 0) + False, 0) f._coeff_stream._target = int_d_self_f return f @@ -3190,7 +3192,8 @@ def log(self): d_self = Stream_function(lambda n: (n + 1) * coeff_stream[n + 1], P.is_sparse(), 0) d_self_quo_self = Stream_cauchy_mul(d_self, - Stream_cauchy_invert(coeff_stream)) + Stream_cauchy_invert(coeff_stream), + P.is_sparse()) int_d_self_quo_self = Stream_function(lambda n: d_self_quo_self[n-1] / R(n), P.is_sparse(), 1) return P.element_class(P, int_d_self_quo_self) From 8730e64d5453ed78fcd93ea403109c81e36e954f Mon Sep 17 00:00:00 2001 From: Johann Birnick Date: Thu, 13 Oct 2022 14:50:54 +0100 Subject: [PATCH 229/414] Improved style. --- .../polynomial/multi_polynomial_ring_base.pyx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index d5fbf8059ac..f59c521a0bb 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -348,7 +348,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): return self.remove_var(x)[str(x)] def multivariate_interpolation(self, bound, *args): - r""" + """ Create a polynomial with specified evaluations. CALL FORMATS: @@ -361,19 +361,24 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): INPUT: - * "bound" -- either an integer bounding the total degree or a list/tuple of integers bounding the degree of the variables + * "bound" -- either an integer bounding the total degree or a + list/tuple of integers bounding the degree of the variables * "points" -- a list/tuple containing the evaluation points * "values" -- a list/tuple containing the desired values at "points" - * "function" -- a evaluable function in n variables, where n is the number of variables of the polynomial ring + * "function" -- a evaluable function in n variables, where n is the + number of variables of the polynomial ring OUTPUT: - 1. A polynomial respecting the bounds and having "values" as values when evaluated at "points". + 1. A polynomial respecting the bounds and having "values" as values + when evaluated at "points". - 2. A polynomial respecting the bounds and having the same values as "function" at exactly so many points so that the polynomial is unique. + 2. A polynomial respecting the bounds and having the same values as + "function" at exactly so many points so that the polynomial is + unique. EXAMPLES:: @@ -407,12 +412,14 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): ALGORITHM: Solves a linear system of equations with the linear algebra module. - If the points are not specified, it samples exactly as many points as needed for a unique solution. + If the points are not specified, it samples exactly as many points + as needed for a unique solution. NOTE: - It will only run if the base ring is a field, even though it might work otherwise as well. - If your base ring is an integral domain, let it run over the fraction field. + It will only run if the base ring is a field, even though it might + work otherwise as well. If your base ring is an integral domain, + let it run over the fraction field. WARNING:: From 291d799f05303a25b4afbbc2b74e87651aad8cc2 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Fri, 14 Oct 2022 16:28:57 +0900 Subject: [PATCH 230/414] Some last reviewer changes to improve some doc and unicode art. --- src/doc/en/reference/references/index.rst | 6 +- src/sage/combinat/diagram.py | 248 +++++++++++++--------- src/sage/combinat/permutation.py | 4 - src/sage/combinat/skew_partition.py | 11 +- src/sage/combinat/skew_tableau.py | 11 +- 5 files changed, 166 insertions(+), 114 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index ee5166235ab..bb2360b3ef5 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5100,10 +5100,10 @@ REFERENCES: **6** (1997), 59-87. .. [RSS] :wikipedia:`Residual_sum_of_squares`, accessed 13th - October 2009. + October 2009. -.. [RS1995] Victor Reiner, Mark Shimozono, "Plactification", - J. Algebraic Combin. **4** (1995), 331-351. +.. [RS1995] Victor Reiner, Mark Shimozono, *Plactification*, + J. Algebraic Combin. **4** (1995), pp. 331-351. .. [RS2012] G. Rudolph and M. Schmidt, "Differential Geometry and Mathematical Physics. Part I. Manifolds, Lie Groups and Hamiltonian Systems", Springer, 2012. diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index b6982155197..a4a4169848a 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -24,7 +24,6 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.categories.sets_cat import Sets from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.combinat.composition import Composition from sage.combinat.partition import Partition @@ -46,10 +45,10 @@ class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): The positions are indexed by rows and columns as in a matrix. For example, a Ferrer's diagram is a diagram obtained from a partition - `\lambda = (\lambda_0, \lambda_1, \ldots, \lambda_\ell)` where the cells are - in rows `i` for `0 \leq i \leq \ell` and the cells in row `i` consist of - `(i,j)` for `0 \leq j < \lambda_i`. In English notation, the indices are - read from top left to bottom right as in a matrix. + `\lambda = (\lambda_0, \lambda_1, \ldots, \lambda_{\ell})`, where the + cells are in rows `i` for `0 \leq i \leq \ell` and the cells in row `i` + consist of `(i,j)` for `0 \leq j < \lambda_i`. In English notation, the + indices are read from top left to bottom right as in a matrix. Indexing conventions are the same as :class:`~sage.combinat.partition.Partition`. Printing the diagram of a @@ -100,12 +99,6 @@ class Diagram(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): . . . . . . . . . . . . . . . . . . - - TESTS:: - - sage: from sage.combinat.diagram import Diagrams - sage: D = Diagrams().an_element() - sage: TestSuite(D).run() """ @staticmethod def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=True): @@ -123,6 +116,8 @@ def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=True): def __init__(self, parent, cells, n_rows=None, n_cols=None, check=True): r""" + Initialize ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import Diagram @@ -133,6 +128,7 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=True): 4 sage: D1.ncols() 4 + sage: TestSuite(D1).run() We can specify the number of rows and columns explicitly, in case they are supposed to be empty:: @@ -147,6 +143,7 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=True): . O . . . . . . . . . . O . . + sage: TestSuite(D2).run() """ self._cells = frozenset(cells) @@ -155,8 +152,8 @@ def __init__(self, parent, cells, n_rows=None, n_cols=None, check=True): N_rows = max(c[0] for c in self._cells) N_cols = max(c[1] for c in self._cells) else: # if there are no cells - N_rows = 0 - N_cols = 0 + N_rows = -1 + N_cols = -1 if n_rows is not None: if n_rows <= N_rows: @@ -198,8 +195,13 @@ def pp(self): . . . . . . . . . . . . . . . . . . + sage: Diagram([]).pp() + - """ - print(self._pretty_print(), end='') + if self._n_rows == 0 or self._n_cols == 0: + print('-') + return + print("\n".join(self._pretty_print())) def _ascii_art_(self): r""" @@ -223,9 +225,13 @@ def _ascii_art_(self): . . . . . . . . . . . . . . . . . . + sage: ascii_art(Diagram([])) + - """ from sage.typeset.ascii_art import ascii_art - return ascii_art(self._pretty_print()) + if self._n_rows == 0 or self._n_cols == 0: + return ascii_art("-") + return ascii_art("\n".join(self._pretty_print())) def _unicode_art_(self): r""" @@ -239,19 +245,45 @@ def _unicode_art_(self): sage: from sage.combinat.diagram import Diagram sage: unicode_art(Diagram([(0,0), (0,3), (2,2), (2,4)])) - ☒☐☐☒☐ - ☐☐☐☐☐ - ☐☐☒☐☒ + ┌─┬─┬─┬─┬─┐ + │X│ │ │X│ │ + ├─┼─┼─┼─┼─┤ + │ │ │ │ │ │ + ├─┼─┼─┼─┼─┤ + │ │ │X│ │X│ + └─┴─┴─┴─┴─┘ sage: unicode_art(Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6)) - ☒☐☐☒☐☐ - ☐☐☐☐☐☐ - ☐☐☒☐☒☐ - ☐☐☐☐☐☐ - ☐☐☐☐☐☐ - ☐☐☐☐☐☐ + ┌─┬─┬─┬─┬─┬─┐ + │X│ │ │X│ │ │ + ├─┼─┼─┼─┼─┼─┤ + │ │ │ │ │ │ │ + ├─┼─┼─┼─┼─┼─┤ + │ │ │X│ │X│ │ + ├─┼─┼─┼─┼─┼─┤ + │ │ │ │ │ │ │ + ├─┼─┼─┼─┼─┼─┤ + │ │ │ │ │ │ │ + ├─┼─┼─┼─┼─┼─┤ + │ │ │ │ │ │ │ + └─┴─┴─┴─┴─┴─┘ + sage: unicode_art(Diagram([])) + ∅ """ from sage.typeset.unicode_art import unicode_art - return unicode_art(self._pretty_print('☒', '☐')) + if self._n_rows == 0 or self._n_cols == 0: + return unicode_art("∅") + + ndivs = self._n_cols - 1 + cell = "│X" + empty = "│ " + it = self._pretty_print(cell, empty) + ret = "┌─" + "┬─"*ndivs + "┐" + ret += "\n" + next(it) + "│" + for row in it: + ret += "\n├─" + "┼─"*ndivs + "┤" + ret += "\n" + row + "│" + ret += "\n└─" + "┴─"*ndivs + "┘" + return unicode_art(ret) def _pretty_print(self, cell='O ', empty='. '): r""" @@ -264,25 +296,24 @@ def _pretty_print(self, cell='O ', empty='. '): EXAMPLES:: sage: from sage.combinat.diagram import Diagram - sage: Diagram([(0,0), (0,3), (2,2), (2,4)])._pretty_print('x ','. ') - 'x . . x . \n. . . . . \n. . x . x \n' - sage: Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6)._pretty_print('x ','. ') - 'x . . x . . \n. . . . . . \n. . x . x . \n. . . . . . \n. . . . . . \n. . . . . . \n' + sage: "\n".join(Diagram([(0,0), (0,3), (2,2), (2,4)])._pretty_print('x ','. ')) + 'x . . x . \n. . . . . \n. . x . x ' + sage: "\n".join(Diagram([(0,0), (0,3), (2,2), (2,4)], n_rows=6, n_cols=6)._pretty_print('x ','. ')) + 'x . . x . . \n. . . . . . \n. . x . x . \n. . . . . . \n. . . . . . \n. . . . . . ' """ - output_str = '' - for i in range(self._n_rows): + output_str = '' for j in range(self._n_cols): if (i, j) in self: output_str += cell else: output_str += empty - output_str += '\n' - - return output_str + yield output_str def _latex_(self): r""" + Return a latex representation of ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import Diagram @@ -296,9 +327,8 @@ def _latex_(self): &&\lr{\phantom{x}}&&\lr{\phantom{x}}\\\cline{3-3}\cline{5-5} \end{array}$} } - """ - if not self.cells(): + if self._n_rows == 0 or self._n_cols == 0: return "{\\emptyset}" lr = r'\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}}' @@ -330,7 +360,7 @@ def end_line(r): def number_of_rows(self): r""" - Return the total number of rows of the cell. + Return the total number of rows of ``self``. EXAMPLES: @@ -365,7 +395,7 @@ def number_of_rows(self): def number_of_cols(self): r""" - Return the total number of rows of the cell. + Return the total number of rows of ``self``. EXAMPLES: @@ -391,7 +421,6 @@ def number_of_cols(self): . . . . . . . . O . . . """ - return self._n_cols ncols = number_of_cols @@ -480,6 +509,8 @@ class Diagrams(UniqueRepresentation, Parent): """ def __init__(self, category=None): r""" + Initialize ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import Diagrams @@ -490,11 +521,12 @@ def __init__(self, category=None): sage: TestSuite(Dgms).run() """ - Parent.__init__(self, category=InfiniteEnumeratedSets().or_subcategory(category)) def __iter__(self): r""" + Iterate over ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import Diagrams @@ -558,6 +590,8 @@ def __iter__(self): def _repr_(self): r""" + Return a string representation of ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import Diagrams @@ -568,6 +602,8 @@ def _repr_(self): def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=True): r""" + Cosntruct an element of ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import Diagrams @@ -616,6 +652,8 @@ def _element_constructor_(self, cells, n_rows=None, n_cols=None, check=True): def _an_element_(self): r""" + Return an element of ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import Diagrams @@ -632,7 +670,7 @@ def _an_element_(self): def from_polyomino(self, p): r""" Create the diagram corresponding to a 2d - :class:`~sage.combinat.tiling.Polyomino.` + :class:`~sage.combinat.tiling.Polyomino` EXAMPLES:: @@ -649,22 +687,22 @@ def from_polyomino(self, p): O . . O O O - The method only works for 2d `Polyomino`s:: + This only works for a 2d :class:`~sage.combinat.tiling.Polyomino`:: sage: p = Polyomino([(0,0,0), (0,1,0), (1,1,0), (1,1,1)], color='blue') sage: Diagrams().from_polyomino(p) Traceback (most recent call last): ... - ValueError: Dimension of the polyomino must be 2 + ValueError: the polyomino must be 2 dimensional """ if not p._dimension == 2: - raise ValueError("Dimension of the polyomino must be 2") + raise ValueError("the polyomino must be 2 dimensional") cells = list(map(tuple, p)) return self.element_class(self, cells) def from_composition(self, alpha): r""" - Create the diagram corresponding to a weak composition `alpha \vDash n`. + Create the diagram corresponding to a weak composition `\alpha \vDash n`. EXAMPLES:: @@ -711,7 +749,6 @@ def from_zero_one_matrix(self, M, check=True): O . . O . . . . . - """ # check matrix is zero-one n_rows, n_cols = M.dimensions() @@ -759,8 +796,7 @@ class NorthwestDiagram(Diagram, metaclass=InheritComparisonClasscallMetaclass): O . . """ @staticmethod - def __classcall_private__(self, cells, n_rows=None, - n_cols=None, check=True): + def __classcall_private__(self, cells, n_rows=None, n_cols=None, check=True): """ Normalize input to ensure a correct parent. This method also allows one to specify whether or not to check the northwest property for the @@ -816,15 +852,16 @@ def check(self): def peelable_tableaux(self): r""" + Return the set of peelable tableaux whose diagram is ``self``. + For a fixed northwest diagram `D`, we say that a Young tableau `T` is `D`-peelable if: - - - the row indices of the cells in the first column of `D` are - the entries in an initial segment in the first column of `T` and - - - the tableau `Q` obtained by removing those cells from `T` and playing - jeu de taquin is `D-C`-peelable, where `D-C` is the diagram formed by - forgetting the first column of `D`. + + 1. the row indices of the cells in the first column of `D` are + the entries in an initial segment in the first column of `T` and + 2. the tableau `Q` obtained by removing those cells from `T` and playing + jeu de taquin is `(D-C)`-peelable, where `D-C` is the diagram formed + by forgetting the first column of `D`. Reiner and Shimozono [RS1995]_ showed that the number `\operatorname{red}(w)` of reduced words of a permutation `w` may be @@ -833,7 +870,7 @@ def peelable_tableaux(self): .. MATH:: - \operatorname{red}(w) = \sum_{T} f_{\operatorname{shape} T} + \operatorname{red}(w) = \sum_{T} f_{\operatorname{shape} T}, where the sum runs over the `D(w)`-peelable tableaux `T` and `f_\lambda` is the number of standard Young tableaux of shape `\lambda` (which may @@ -865,7 +902,7 @@ def peelable_tableaux(self): sage: NWD.peelable_tableaux() {[[1], [3]]} - From [RS1995]_ we know that there is only one peelable tableau for the + From [RS1995]_, we know that there is only one peelable tableau for the Rothe diagram of the permutation (in one line notation) `251643`:: sage: D = NorthwestDiagram([(1, 2), (1, 3), (3, 2), (3, 3), (4, 2)]) @@ -1026,10 +1063,9 @@ class NorthwestDiagrams(Diagrams): r""" Diagrams satisfying the northwest property. - A diagram is a - *northwest diagram* if it satsifies the property that: the presence of two - cells `(i_1, j_1)` and `(i_2, j_2)` in a diagram `D` implies the presence of - the cell `(\min(i_1, i_2), \min(j_1, j_2))`. + A diagram `D` is a *northwest diagram* if for every two cells `(i_1, j_1)` + and `(i_2, j_2)` in `D` then there exists the cell + `(\min(i_1, i_2), \min(j_1, j_2)) \in D`. EXAMPLES:: @@ -1060,8 +1096,8 @@ class NorthwestDiagrams(Diagrams): ... ValueError: diagram is not northwest - However, this behavior can be turned off if you are confident that you are - providing a northwest diagram:: + However, this behavior can be turned off if you are confident that + you are providing a northwest diagram:: sage: N = NorthwestDiagram([(0, 0), (0, 10), (5, 0), ....: (1, 1), (0, 1), (1, 0)], @@ -1074,8 +1110,8 @@ class NorthwestDiagrams(Diagrams): . . . . . . . . . . . O . . . . . . . . . . - Note that arbitrary diagrams which happen to be northwest diagrams only live - in the parent of :class:`Diagrams`:: + Note that arbitrary diagrams which happen to be northwest diagrams + only live in the parent of :class:`Diagrams`:: sage: D = Diagram([(0, 0), (0, 10), (5, 0), (1, 1), (0, 1), (1, 0)]) sage: D.pp() @@ -1115,8 +1151,8 @@ class NorthwestDiagrams(Diagrams): D(\omega) = \{(\omega_j, i) : i \omega_j \}. - We can construct one by calling :meth:`rothe_diagram` method on the parent - class :class:`NorthwestDiagrams`:: + We can construct one by calling :meth:`rothe_diagram` method on the set + of all :class:`~sage.combinat.diagram.NorthwestDiagrams`:: sage: w = Permutations(4)([4,3,2,1]) sage: NorthwestDiagrams().rothe_diagram(w).pp() @@ -1125,9 +1161,9 @@ class :class:`NorthwestDiagrams`:: O . . . . . . . - To turn a Ferrers diagram into a northwest diagram, we may call the - :meth:`from_partition` method. This will return a Ferrer's diagram in the - parent of all northwest diagrams. For many use-cases it is probably better + To turn a Ferrers diagram into a northwest diagram, we may call + :meth:`from_partition`. This will return a Ferrer's diagram in the + set of all northwest diagrams. For many use-cases it is probably better to get Ferrer's diagrams by the corresponding method on partitons, namely :meth:`sage.combinat.partitions.Partitions.ferrers_diagram`:: @@ -1165,6 +1201,8 @@ class :class:`NorthwestDiagrams`:: def _repr_(self): r""" + Return a string representation of ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import NorthwestDiagrams @@ -1175,6 +1213,8 @@ def _repr_(self): def _an_element_(self): r""" + Return an element of ``self``. + EXAMPLES:: sage: from sage.combinat.diagram import NorthwestDiagrams @@ -1194,8 +1234,9 @@ def rothe_diagram(self, w): r""" Return the Rothe diagram of ``w``. - One particular way of constructing a northwest diagram from a permutation is - by constructing its Rothe diagram. Formally, if `\omega` is a permutation, + We construct a northwest diagram from a permutation by + constructing its Rothe diagram. Formally, if `\omega` is + a :class:`~sage.combinat.permutation.Permutation` then the Rothe diagram `D(\omega)` is the diagram whose cells are .. MATH:: @@ -1205,28 +1246,16 @@ def rothe_diagram(self, w): Informally, one can construct the Rothe diagram by starting with all `n^2` possible cells, and then deleting the cells `(i, \omega(i))` as well as all cells to the right and below. (These are sometimes called - "death rays".) To compute a Rothe diagram in Sage, start with a - :class:`~sage.combinat.permutation.Permutation`:: + "death rays".) - sage: w = Permutations(8)([2,5,4,1,3,6,7,8]) + .. SEEALSO:: - Then call :func:`RotheDiagram` on ``w``:: - - sage: from sage.combinat.diagram import RotheDiagram - sage: RotheDiagram(w).pp() - O . . . . . . . - O . O O . . . . - O . O . . . . . - . . . . . . . . - . . . . . . . . - . . . . . . . . - . . . . . . . . - . . . . . . . . + :func:`~sage.combinat.diagram.RotheDiagram` EXAMPLES:: - sage: w = Permutations(3)([2,1,3]) sage: from sage.combinat.diagram import NorthwestDiagrams + sage: w = Permutations(3)([2,1,3]) sage: NorthwestDiagrams().rothe_diagram(w).pp() O . . . . . @@ -1235,6 +1264,17 @@ def rothe_diagram(self, w): O . . . . . . . . + + sage: w = Permutations(8)([2,5,4,1,3,6,7,8]) + sage: NorthwestDiagrams().rothe_diagram(w).pp() + O . . . . . . . + O . O O . . . . + O . O . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . + . . . . . . . . """ return RotheDiagram(w) @@ -1359,7 +1399,7 @@ def from_parallelogram_polyomino(self, p): def RotheDiagram(w): r""" - The Rothe diagram of a permutation ``w`` + The Rothe diagram of a permutation ``w``. EXAMPLES:: @@ -1381,7 +1421,21 @@ def RotheDiagram(w): sage: D.parent() Combinatorial northwest diagrams - Currently, only elements of the parent + Some other examples:: + + sage: RotheDiagram([2, 1, 4, 3]).pp() + O . . . + . . . . + . . O . + . . . . + + sage: RotheDiagram([4, 1, 3, 2]).pp() + O O O . + . . . . + . O . . + . . . . + + Currently, only elements of the set of :class:`sage.combinat.permutations.Permutations` are supported. In particular, elements of permutation groups are not supported:: @@ -1389,8 +1443,7 @@ def RotheDiagram(w): sage: RotheDiagram(w) Traceback (most recent call last): ... - ValueError: w must be a Permutation - + ValueError: w must be a permutation TESTS:: @@ -1403,15 +1456,16 @@ def RotheDiagram(w): . . . . . . . . . . """ - - from sage.misc.mrange import cartesian_product_iterator - - if w not in Permutations(): - raise ValueError('w must be a Permutation') + P = Permutations() + if w not in P: + raise ValueError('w must be a permutation') + w = P(w) N = w.size() winv = w.inverse() + from sage.misc.mrange import cartesian_product_iterator cells = [c for c in cartesian_product_iterator((range(N), range(N))) if c[0]+1 < winv(c[1]+1) and c[1]+1 < w(c[0]+1)] return NorthwestDiagram(cells, n_rows=N, n_cols=N, check=False) + diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index d8fb066a72e..3228a79623a 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -200,11 +200,8 @@ AUTHORS: - Mike Hansen - - Dan Drake (2008-04-07): allow Permutation() to take lists of tuples - - Sébastien Labbé (2009-03-17): added robinson_schensted_inverse - - Travis Scrimshaw: * (2012-08-16): ``to_standard()`` no longer modifies input @@ -216,7 +213,6 @@ - Darij Grinberg (2013-09-07): added methods; ameliorated :trac:`14885` by exposing and documenting methods for global-independent multiplication. - - Travis Scrimshaw (2014-02-05): Made :class:`StandardPermutations_n` a finite Weyl group to make it more uniform with :class:`SymmetricGroup`. Added ability to compute the conjugacy classes. diff --git a/src/sage/combinat/skew_partition.py b/src/sage/combinat/skew_partition.py index 3f60e008c2e..91644f47462 100644 --- a/src/sage/combinat/skew_partition.py +++ b/src/sage/combinat/skew_partition.py @@ -740,13 +740,15 @@ def conjugate(self): def outer_corners(self): """ - Return a list of the outer corners of ``self``. These are corners - which are contained inside of the shape. For the corners which are - outside of the shape, use :meth:`outside_corners`. + Return a list of the outer corners of ``self``. + + These are corners that are contained inside of the shape. + For the corners which are outside of the shape, + use :meth:`outside_corners`. .. WARNING:: - In the case that `self` is an honest (rather than skew) partition, + In the case that ``self`` is an honest (rather than skew) partition, these are the :meth:`~sage.combinat.partition.Partition.corners` of the outer partition. In the language of [Sag2001]_ these would be the "inner corners" of the outer partition. @@ -1965,3 +1967,4 @@ def __iter__(self): from sage.misc.persist import register_unpickle_override register_unpickle_override('sage.combinat.skew_partition', 'SkewPartition_class', SkewPartition) + diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 840a6864d0f..3eeae3d8d56 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -910,9 +910,10 @@ def slide(self, corner=None, return_vacated=False): def backward_slide(self, corner=None): r""" - Apply a backward jeu de taquin slide on the specified outside corner. + Apply a backward jeu de taquin slide on the specified outside + ``corner`` of ``self``. - Backward jeu de taquin slides are defined in section 3.7 of + Backward jeu de taquin slides are defined in Section 3.7 of [Sag2001]_. .. WARNING:: @@ -990,8 +991,8 @@ def backward_slide(self, corner=None): outer_outisde_corners = self.outer_shape().outside_corners() if corner is not None: if tuple(corner) not in outer_outisde_corners: - raise ValueError("corner must be an outside corner \ - of the outer shape") + raise ValueError("corner must be an outside corner" + " of the outer shape") else: if not outer_outisde_corners: return self @@ -1007,8 +1008,6 @@ def backward_slide(self, corner=None): new_st.append(list()) new_st[i].append(None) - - while (i, j) not in inner_outside_corners: # get the value of the cell above the temporarily empty cell (if # it exists) From 382926c5cabb572bf912de7ce1137f07d5cc42eb Mon Sep 17 00:00:00 2001 From: "Trevor K. Karn" Date: Sat, 15 Oct 2022 09:04:59 -0500 Subject: [PATCH 231/414] Fix unused variable --- src/sage/combinat/partition.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index b1f9810beaf..491108c180d 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -4799,7 +4799,6 @@ def vertical_border_strip_cells(self, k): return [] shelf = [] - res = [] i = 0 ell = len(self._list) while i < ell: @@ -4856,7 +4855,6 @@ def horizontal_border_strip_cells(self, k): return list() L = self._list - res = [] shelf = [k] # the number of boxes which will fit in a row mapping = [0] # a record of the rows for i in range(len(L)-1): From b39ffd2e38fe788cc674c8be22d2cbe946d2f4ab Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 15 Oct 2022 17:24:08 -0700 Subject: [PATCH 232/414] build/pkgs/openssl/spkg-configure.m4: Also require openssl if curl needs to be built --- build/pkgs/openssl/spkg-configure.m4 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build/pkgs/openssl/spkg-configure.m4 b/build/pkgs/openssl/spkg-configure.m4 index fd2d257d721..51dcb7de779 100644 --- a/build/pkgs/openssl/spkg-configure.m4 +++ b/build/pkgs/openssl/spkg-configure.m4 @@ -56,9 +56,10 @@ On Cygwin, openssl must be installed as a system package. This is an error."]) ]) ], [dnl REQUIRED-CHECK AC_REQUIRE([SAGE_SPKG_CONFIGURE_PYTHON3]) - dnl openssl is a dependency only of python3; so if we use system python3, + AC_REQUIRE([SAGE_SPKG_CONFIGURE_CURL]) + dnl openssl is a dependency only of python3 and curl; so if we use system python3 and curl, dnl we do not require it. (In particular, we do not need a specific version.) - AS_IF([test x$sage_spkg_install_python3 = xno], [ + AS_IF([test x$sage_spkg_install_python3 = xno -a x$sage_spkg_install_curl = xno], [ sage_require_openssl=no ]) ]) From 8990d53f335631f4926cfb1c1e18bfa9ecda5689 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 10:55:58 -0700 Subject: [PATCH 233/414] .github/workflows/docker.yml: Add option free_disk_space --- .github/workflows/ci-linux.yml | 7 +++++++ .github/workflows/docker.yml | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index d5935789219..17f0217ead7 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -51,6 +51,7 @@ jobs: with: # Build incrementally from previous stage (pre) incremental: true + free_disk_space: true from_docker_repository: ghcr.io/${{ github.repository }}/ from_docker_target: "with-targets-pre" docker_targets: "with-targets with-targets-optional" @@ -82,6 +83,7 @@ jobs: with: # Build incrementally from previous stage (pre) incremental: true + free_disk_space: true from_docker_repository: ghcr.io/${{ github.repository }}/ from_docker_target: "with-targets-pre" docker_targets: "with-targets with-targets-optional" @@ -97,6 +99,7 @@ jobs: needs: [minimal] uses: ./.github/workflows/docker.yml with: + free_disk_space: true # Build from scratch docker_targets: "with-system-packages configured with-targets-pre" # FIXME: duplicated from env.TARGETS @@ -111,6 +114,7 @@ jobs: uses: ./.github/workflows/docker.yml with: incremental: true + free_disk_space: true from_docker_repository: ghcr.io/${{ github.repository }}/ from_docker_target: "with-targets-pre" tox_packages_factors: >- @@ -125,6 +129,7 @@ jobs: uses: ./.github/workflows/docker.yml with: incremental: true + free_disk_space: true from_docker_repository: ghcr.io/${{ github.repository }}/ from_docker_target: "with-targets-pre" tox_packages_factors: >- @@ -138,6 +143,7 @@ jobs: uses: ./.github/workflows/docker.yml with: incremental: true + free_disk_space: true from_docker_repository: ghcr.io/${{ github.repository }}/ from_docker_target: "with-targets-pre" tox_packages_factors: >- @@ -151,6 +157,7 @@ jobs: uses: ./.github/workflows/docker.yml with: incremental: true + free_disk_space: true from_docker_repository: ghcr.io/${{ github.repository }}/ from_docker_target: "with-targets-pre" tox_packages_factors: >- diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b9af3ea60a1..6b387dad6c8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -65,6 +65,9 @@ on: max_parallel: type: number default: 24 + free_disk_space: + default: false + type: boolean # # Publishing to GitHub Packages # @@ -150,6 +153,7 @@ jobs: dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 50 sudo apt-get --fix-broken --yes remove $(dpkg-query -f '${Package}\n' -W | grep -E '^(ghc-|google-cloud-sdk|google-chrome|firefox|mysql-server|dotnet-sdk|hhvm|mono)') || echo "(error ignored)" df -h + if: inputs.free_disk_space - name: Check out git-trac-command uses: actions/checkout@v2 with: From 09244b05468c5b7b407074a60cbd3452eebd60a7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 18:01:55 -0700 Subject: [PATCH 234/414] tox.ini (python3.x): Do not clobber user-provided EXTRA_SAGE_PACKAGES --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 3e815e2d060..7a50b34095b 100644 --- a/tox.ini +++ b/tox.ini @@ -511,7 +511,7 @@ setenv = CONFIG_CONFIGURE_ARGS_1=--with-system-python3=yes python3_spkg: CONFIG_CONFIGURE_ARGS_1=--without-system-python3 python3.8,python3.9,python3.10,python3.11: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=python{env:PYTHON_MAJOR}.{env:PYTHON_MINOR} - python3.8,python3.9,python3.10,python3.11: EXTRA_SAGE_PACKAGES=_python{env:PYTHON_MAJOR}.{env:PYTHON_MINOR} _bootstrap liblzma bzip2 libffi libpng + python3.8,python3.9,python3.10,python3.11: EXTRA_SAGE_PACKAGES_5=_python{env:PYTHON_MAJOR}.{env:PYTHON_MINOR} _bootstrap liblzma bzip2 libffi libpng macos-python3_xcode: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/usr/bin/python3 macos-{python3_xcode,nohomebrew}-{python3.8}: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/{env:PYTHON_MAJOR}.{env:PYTHON_MINOR}/bin/python3 # Homebrew keg installs @@ -520,7 +520,7 @@ setenv = macos-python3_pythonorg: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/Library/Frameworks/Python.framework/Versions/{env:PYTHON_MAJOR}.{env:PYTHON_MINOR}/bin/python3 # https://github.com/pypa/manylinux manylinux-standard: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/opt/python/cp{env:PYTHON_MAJOR}{env:PYTHON_MINOR}-cp{env:PYTHON_MAJOR}{env:PYTHON_MINOR}/bin/python3 - manylinux-{python3.8,python3.9,python3.10,python3.11}: EXTRA_SAGE_PACKAGES=_bootstrap xz bzip2 libffi libpng + manylinux-{python3.8,python3.9,python3.10,python3.11}: EXTRA_SAGE_PACKAGES_5=_bootstrap xz bzip2 libffi libpng conda: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=python3 # # - toolchain @@ -573,7 +573,7 @@ setenv = # # Resulting EXTRA_SAGE_PACKAGES # - ALL_EXTRA_SAGE_PACKAGES={env:EXTRA_SAGE_PACKAGES_0:} {env:EXTRA_SAGE_PACKAGES_1:} {env:EXTRA_SAGE_PACKAGES_2:} {env:EXTRA_SAGE_PACKAGES_3:} {env:EXTRA_SAGE_PACKAGES_4:} {env:EXTRA_SAGE_PACKAGES:} + ALL_EXTRA_SAGE_PACKAGES={env:EXTRA_SAGE_PACKAGES_0:} {env:EXTRA_SAGE_PACKAGES_1:} {env:EXTRA_SAGE_PACKAGES_2:} {env:EXTRA_SAGE_PACKAGES_3:} {env:EXTRA_SAGE_PACKAGES_4:} {env:EXTRA_SAGE_PACKAGES_5:} {env:EXTRA_SAGE_PACKAGES:} # environment will be skipped if regular expression does not match against the sys.platform string platform = From 9fb4eac6f4a87fa73fa79a1b173cd09e590ab2fa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 18:07:12 -0700 Subject: [PATCH 235/414] .github/workflows/docker.yml: Add option extra_sage_packages --- .github/workflows/docker.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6b387dad6c8..e58732fa810 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -62,6 +62,10 @@ on: ["minimal", "standard", ] + extra_sage_packages: + description: 'Extra Sage packages to install as system packages' + type: string + default: "" max_parallel: type: number default: 24 @@ -132,7 +136,7 @@ jobs: FROM_DOCKER_TARGET: ${{ inputs.from_docker_target }} FROM_DOCKER_TAG: ${{ inputs.from_docker_tag }} EXTRA_CONFIGURE_ARGS: --enable-fat-binary - + EXTRA_SAGE_PACKAGES: ${{ inputs.extra_sage_packages }} steps: - name: Check out SageMath uses: actions/checkout@v2 From 3897adb6aa1c59f4410315ea813e4ccc08cc3df0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 21:00:05 -0700 Subject: [PATCH 236/414] tox.ini (manylinux-minimal): Provide system liblzma so that system python can be accepted; not system xz --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7a50b34095b..25d9e9f70ad 100644 --- a/tox.ini +++ b/tox.ini @@ -520,7 +520,7 @@ setenv = macos-python3_pythonorg: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/Library/Frameworks/Python.framework/Versions/{env:PYTHON_MAJOR}.{env:PYTHON_MINOR}/bin/python3 # https://github.com/pypa/manylinux manylinux-standard: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/opt/python/cp{env:PYTHON_MAJOR}{env:PYTHON_MINOR}-cp{env:PYTHON_MAJOR}{env:PYTHON_MINOR}/bin/python3 - manylinux-{python3.8,python3.9,python3.10,python3.11}: EXTRA_SAGE_PACKAGES_5=_bootstrap xz bzip2 libffi libpng + manylinux-{python3.8,python3.9,python3.10,python3.11}: EXTRA_SAGE_PACKAGES_5=_bootstrap liblzma bzip2 libffi libpng conda: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=python3 # # - toolchain From f19e4b69794a2b137e1371c61ac220acc85a7510 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 18 Oct 2022 16:49:52 -0700 Subject: [PATCH 237/414] space after comma --- src/sage/algebras/fusion_rings/f_matrix.py | 274 +++++++++--------- .../fast_parallel_fmats_methods.pyx | 104 +++---- .../fast_parallel_fusion_ring_braid_repn.pyx | 88 +++--- src/sage/algebras/fusion_rings/fusion_ring.py | 230 +++++++-------- .../algebras/fusion_rings/poly_tup_engine.pyx | 53 ++-- .../algebras/fusion_rings/shm_managers.pyx | 184 ++++++------ 6 files changed, 467 insertions(+), 466 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 61053cc62af..9ec23340e66 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -1,5 +1,5 @@ r""" -F-Matries of fusion rings +F-Matrices of Fusion Rings """ # **************************************************************************** # Copyright (C) 2019 Daniel Bump @@ -96,7 +96,7 @@ class FMatrix(SageObject): +------------------------+----------+ | `D_r, r\geq 4` | `\leq 2` | +------------------------+----------+ - | `G_2,F_4,E_6,E_7` | `\leq 2` | + | `G_2, F_4, E_6, E_7` | `\leq 2` | +------------------------+----------+ | `E_8` | `\leq 3` | +------------------------+----------+ @@ -119,15 +119,15 @@ class FMatrix(SageObject): .. MATH:: - \text{Hom}(D,(A\otimes B)\otimes C) - \to \text{Hom}(D,A\otimes(B\otimes C)) + \text{Hom}(D, (A\otimes B)\otimes C) + \to \text{Hom}(D, A\otimes(B\otimes C)) by a matrix `F^{ABC}_D`. This depends on a pair of additional simple objects `X` and `Y`. Indeed, we can - get a basis for `\text{Hom}(D,(A\otimes B)\otimes C)` + get a basis for `\text{Hom}(D, (A\otimes B)\otimes C)` indexed by simple objects `X` in which the corresponding homomorphism factors through `X\otimes C`, and similarly - `\text{Hom}(D,A\otimes(B\otimes C))` has a basis indexed + `\text{Hom}(D, A\otimes(B\otimes C))` has a basis indexed by `Y`, in which the basis vector factors through `A\otimes Y`. See [TTWL2009]_ for an introduction to this topic, @@ -139,7 +139,7 @@ class FMatrix(SageObject): The F-matrix is only determined up to a *gauge*. This is a family of embeddings `C \to A\otimes B` for - simple objects `A,B,C` such that `\text{Hom}(C, A\otimes B)` + simple objects `A, B, C` such that `\text{Hom}(C, A\otimes B)` is nonzero. Changing the gauge changes the F-matrix though not in a very essential way. By varying the gauge it is possible to make the F-matrices unitary, or it is possible @@ -151,8 +151,8 @@ class FMatrix(SageObject): EXAMPLES:: sage: I = FusionRing("E8", 2, conjugate=True) - sage: I.fusion_labels(["i0","p","s"],inject_variables=True) - sage: f = FMatrix(I,inject_variables=True); f + sage: I.fusion_labels(["i0", "p", "s"], inject_variables=True) + sage: f = FMatrix(I, inject_variables=True); f creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients @@ -170,7 +170,7 @@ class FMatrix(SageObject): EXAMPLES:: - sage: f.fmatrix(s,s,s,s) + sage: f.fmatrix(s, s, s, s) [fx10 fx11] [fx12 fx13] @@ -179,15 +179,15 @@ class FMatrix(SageObject): ``fx10``, ``fx11``, ``fx12``, ``fx13``. The task is to solve for these. - As explained above The F-matrix `(F^{ABC}_D)_{X,Y}` + As explained above The F-matrix `(F^{ABC}_D)_{X, Y}` two other variables `X` and `Y`. We have methods to - tell us (depending on `A,B,C,D`) what the possibilities + tell us (depending on `A, B, C, D`) what the possibilities for these are. In this example with `A=B=C=D=s` both `X` and `Y` are allowed to be `i_0` or `s`. :: - sage: f.f_from(s,s,s,s), f.f_to(s,s,s,s) + sage: f.f_from(s, s, s, s), f.f_to(s, s, s, s) ([i0, p], [i0, p]) The last two statments show that the possible values of @@ -238,10 +238,10 @@ class FMatrix(SageObject): We now have access to the values of the F-matrix using the methods :meth:`fmatrix` and :meth:`fmat`:: - sage: f.fmatrix(s,s,s,s) + sage: f.fmatrix(s, s, s, s) [(-1/2*zeta128^48 + 1/2*zeta128^16) 1] [ 1/2 (1/2*zeta128^48 - 1/2*zeta128^16)] - sage: f.fmat(s,s,s,s,p,p) + sage: f.fmat(s, s, s, s, p, p) (1/2*zeta128^48 - 1/2*zeta128^16) :meth:`find_orthogonal_solution` is much more powerful @@ -258,12 +258,12 @@ class FMatrix(SageObject): :: - sage: f = FMatrix(FusionRing("B3",2)) - sage: f.find_orthogonal_solution(verbose=False,checkpoint=True) # not tested (~100 s) + sage: f = FMatrix(FusionRing("B3", 2)) + sage: f.find_orthogonal_solution(verbose=False, checkpoint=True) # not tested (~100 s) sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # not tested True - sage: f = FMatrix(FusionRing("G2",2)) + sage: f = FMatrix(FusionRing("G2", 2)) sage: f.find_orthogonal_solution(verbose=False) # long time (~11 s) sage: f.field() # long time Algebraic Field @@ -274,7 +274,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab EXAMPLES:: - sage: f = FMatrix(FusionRing("B3",2)) + sage: f = FMatrix(FusionRing("B3", 2)) sage: TestSuite(f).run(skip="_test_pickling") """ self._FR = fusion_ring @@ -284,9 +284,9 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab raise NotImplementedError("FMatrix is only available for multiplicity free FusionRings") #Set up F-symbols entry by entry n_vars = self.findcases() - self._poly_ring = PolynomialRing(self._FR.field(),n_vars,var_prefix) + self._poly_ring = PolynomialRing(self._FR.field(), n_vars, var_prefix) if inject_variables: - print("creating variables %s%s..%s%s"%(var_prefix,1,var_prefix,n_vars)) + print("creating variables %s%s..%s%s"%(var_prefix, 1, var_prefix, n_vars)) self._poly_ring.inject_variables(get_main_globals()) self._idx_to_sextuple, self._fvars = self.findcases(output=True) @@ -403,8 +403,8 @@ def _reset_solver_state(self): n = self._poly_ring.ngens() self._var_degs = [0] * n self._kp = {} - self._ks = KSHandler(n,self._field) - self._singles = self.get_fvars_by_size(1,indices=True) + self._ks = KSHandler(n, self._field) + self._singles = self.get_fvars_by_size(1, indices=True) self._nnz = self._get_known_nonz() #Clear relevant caches @@ -414,12 +414,12 @@ def _reset_solver_state(self): def fmat(self, a, b, c, d, x, y, data=True): r""" - Return the F-Matrix coefficient `(F^{a,b,c}_d)_{x,y}`. + Return the F-Matrix coefficient `(F^{a, b, c}_d)_{x, y}`. EXAMPLES:: - sage: f=FMatrix(FusionRing("G2", 1, fusion_labels=("i0","t"), inject_variables=True)) - sage: [f.fmat(t,t,t,t,x,y) for x in f._FR.basis() for y in f._FR.basis()] + sage: f=FMatrix(FusionRing("G2", 1, fusion_labels=("i0", "t"), inject_variables=True)) + sage: [f.fmat(t, t, t, t, x, y) for x in f._FR.basis() for y in f._FR.basis()] [fx1, fx2, fx3, fx4] sage: f.find_cyclotomic_solution(output=True) Setting up hexagons and pentagons... @@ -433,14 +433,14 @@ def fmat(self, a, b, c, d, x, y, data=True): (t, t, t, t, i0, t): 1, (t, t, t, t, t, i0): (-zeta60^14 + zeta60^6 + zeta60^4 - 1), (t, t, t, t, t, t): (zeta60^14 - zeta60^6 - zeta60^4 + 1)} - sage: [f.fmat(t,t,t,t,x,y) for x in f._FR.basis() for y in f._FR.basis()] + sage: [f.fmat(t, t, t, t, x, y) for x in f._FR.basis() for y in f._FR.basis()] [(-zeta60^14 + zeta60^6 + zeta60^4 - 1), 1, (-zeta60^14 + zeta60^6 + zeta60^4 - 1), (zeta60^14 - zeta60^6 - zeta60^4 + 1)] """ - if (self._FR.Nk_ij(a,b,x) == 0 or self._FR.Nk_ij(x,c,d) == 0 - or self._FR.Nk_ij(b,c,y) == 0 or self._FR.Nk_ij(a,y,d) == 0): + if (self._FR.Nk_ij(a, b, x) == 0 or self._FR.Nk_ij(x, c, d) == 0 + or self._FR.Nk_ij(b, c, y) == 0 or self._FR.Nk_ij(a, y, d) == 0): return 0 #Some known zero F-symbols @@ -463,15 +463,15 @@ def fmat(self, a, b, c, d, x, y, data=True): #Better to use try/except for speed. Somewhat trivial, but worth #hours when method is called ~10^11 times try: - return self._fvars[a,b,c,d,x,y] + return self._fvars[a, b, c, d, x, y] except KeyError: return 0 else: - return (a,b,c,d,x,y) + return (a, b, c, d, x, y) - def fmatrix(self,a,b,c,d): + def fmatrix(self, a, b, c, d): r""" - Return the F-Matrix `F^{a,b,c}_d`. + Return the F-Matrix `F^{a, b, c}_d`. INPUT: @@ -480,23 +480,23 @@ def fmatrix(self,a,b,c,d): EXAMPLES:: sage: f = FMatrix(FusionRing("A1", 2, fusion_labels="c", inject_variables=True)) - sage: f.fmatrix(c1,c1,c1,c1) + sage: f.fmatrix(c1, c1, c1, c1) [fx0 fx1] [fx2 fx3] sage: f.find_cyclotomic_solution(verbose=False); adding equation... fx4 - 1 adding equation... fx10 - 1 - sage: f.f_from(c1,c1,c1,c1) + sage: f.f_from(c1, c1, c1, c1) [c0, c2] - sage: f.f_to(c1,c1,c1,c1) + sage: f.f_to(c1, c1, c1, c1) [c0, c2] - sage: f.fmatrix(c1,c1,c1,c1) + sage: f.fmatrix(c1, c1, c1, c1) [ (1/2*zeta32^12 - 1/2*zeta32^4) (-1/2*zeta32^12 + 1/2*zeta32^4)] [ (1/2*zeta32^12 - 1/2*zeta32^4) (1/2*zeta32^12 - 1/2*zeta32^4)] """ - X = self.f_from(a,b,c,d) - Y = self.f_to(a,b,c,d) - return matrix([[self.fmat(a,b,c,d,x,y) for y in Y] for x in X]) + X = self.f_from(a, b, c, d) + Y = self.f_to(a, b, c, d) + return matrix([[self.fmat(a, b, c, d, x, y) for y in Y] for x in X]) def field(self): r""" @@ -526,7 +526,7 @@ def field(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: f.field() Cyclotomic Field of order 60 and degree 16 sage: f.find_orthogonal_solution(verbose=False) @@ -551,13 +551,13 @@ def FR(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3",1)) + sage: f = FMatrix(FusionRing("D3", 1)) sage: f.FR() The Fusion Ring of Type D3 and level 1 with Integer Ring coefficients """ return self._FR - def findcases(self,output=False): + def findcases(self, output=False): r""" Return unknown F-matrix entries. @@ -567,7 +567,7 @@ def findcases(self,output=False): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1, fusion_labels=("i0","t"))) + sage: f = FMatrix(FusionRing("G2", 1, fusion_labels=("i0", "t"))) sage: f.findcases() 5 sage: f.findcases(output=True) @@ -587,14 +587,14 @@ def findcases(self,output=False): idx_map = dict() ret = dict() id_anyon = self._FR.one() - for (a,b,c,d) in product(self._FR.basis(), repeat=4): + for (a, b, c, d) in product(self._FR.basis(), repeat=4): if a == id_anyon or b == id_anyon or c == id_anyon: continue for x in self.f_from(a, b, c, d): for y in self.f_to(a, b, c, d): if output: v = self._poly_ring.gen(i) - ret[(a,b,c,d,x,y)] = v + ret[(a, b, c, d, x, y)] = v idx_map[i] = (a, b, c, d, x, y) i += 1 if output: @@ -602,7 +602,7 @@ def findcases(self,output=False): else: return i - def f_from(self,a,b,c,d): + def f_from(self, a, b, c, d): r""" Return the possible `x` such that there are morphisms `d \to x \otimes c \to (a \otimes b) \otimes c`. @@ -615,18 +615,18 @@ def f_from(self,a,b,c,d): sage: fr = FusionRing("A1", 3, fusion_labels="a", inject_variables=True) sage: f = FMatrix(fr) - sage: f.fmatrix(a1,a1,a2,a2) + sage: f.fmatrix(a1, a1, a2, a2) [fx6 fx7] [fx8 fx9] - sage: f.f_from(a1,a1,a2,a2) + sage: f.f_from(a1, a1, a2, a2) [a0, a2] - sage: f.f_to(a1,a1,a2,a2) + sage: f.f_to(a1, a1, a2, a2) [a1, a3] """ return [x for x in self._FR.basis() - if self._FR.Nk_ij(a,b,x) != 0 and self._FR.Nk_ij(x,c,d) != 0] + if self._FR.Nk_ij(a, b, x) != 0 and self._FR.Nk_ij(x, c, d) != 0] - def f_to(self,a,b,c,d): + def f_to(self, a, b, c, d): r""" Return the possible `y` such that there are morphisms `d \to a \otimes y \to a \otimes (b \otimes c)`. @@ -650,7 +650,7 @@ def f_to(self,a,b,c,d): [b1, b3, b5] """ return [y for y in self._FR.basis() - if self._FR.Nk_ij(b,c,y) != 0 and self._FR.Nk_ij(a,y,d) != 0] + if self._FR.Nk_ij(b, c, y) != 0 and self._FR.Nk_ij(a, y, d) != 0] #################### ### Data getters ### @@ -660,9 +660,9 @@ def get_fvars(self): r""" Return a dictionary of F-symbols. - The keys are sextuples `(a,b,c,d,x,y)` of basis elements of + The keys are sextuples `(a, b, c, d, x, y)` of basis elements of ``self.FR()`` and the values are the corresponding F-symbols - `(F^{a,b,c}_d)_{xy}`. + `(F^{a, b, c}_d)_{xy}`. These values reflect the current state of a solver's computation. @@ -762,7 +762,7 @@ def get_qqbar_embedding(self): [1] Computing appropriate NumberField... sage: phi = f.get_qqbar_embedding() - sage: phi(f.fmat(g1,g1,g1,g1,g1,g1)).n() + sage: phi(f.fmat(g1, g1, g1, g1, g1, g1)).n() -0.618033988749895 + 1.46674215951686e-29*I """ return self._qqbar_embedding @@ -915,12 +915,12 @@ def largest_fmat_size(self): sage: f.largest_fmat_size() 4 """ - return max(self.fmatrix(*tup).nrows() for tup in product(self._FR.basis(),repeat=4)) + return max(self.fmatrix(*tup).nrows() for tup in product(self._FR.basis(), repeat=4)) - def get_fvars_by_size(self,n,indices=False): + def get_fvars_by_size(self, n, indices=False): r""" Return the set of F-symbols that are entries of an `n \times n` matrix - `F^{a,b,c}_d`. + `F^{a, b, c}_d`. INPUT: @@ -928,7 +928,7 @@ def get_fvars_by_size(self,n,indices=False): - ``indices`` -- boolean (default: ``False``) If ``indices`` is ``False`` (default), - this method returns a set of sextuples `(a,b,c,d,x,y)` identifying + this method returns a set of sextuples `(a, b, c, d, x, y)` identifying the corresponding F-symbol. Each sextuple is a key in the dictionary returned by :meth:`get_fvars`. @@ -938,7 +938,7 @@ def get_fvars_by_size(self,n,indices=False): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2",2), inject_variables=True) + sage: f = FMatrix(FusionRing("A2", 2), inject_variables=True) creating variables fx1..fx287 Defining fx0, ..., fx286 sage: f.largest_fmat_size() @@ -952,9 +952,9 @@ def get_fvars_by_size(self,n,indices=False): """ var_set = set() one = self._FR.one() - for a,b,c,d in product(self._FR.basis(),repeat=4): - X = self.f_from(a,b,c,d) - Y = self.f_to(a,b,c,d) + for a, b, c, d in product(self._FR.basis(), repeat=4): + X = self.f_from(a, b, c, d) + Y = self.f_to(a, b, c, d) if len(X) == n and len(Y) == n: for x in X: for y in Y: @@ -963,7 +963,7 @@ def get_fvars_by_size(self,n,indices=False): trivial |= b == one and x == a and y == c trivial |= c == one and x == d and y == b if not trivial: - var_set.add((a,b,c,d,x,y)) + var_set.add((a, b, c, d, x, y)) if indices: sext_to_idx = {v: k for k, v in self._idx_to_sextuple.items()} return {sext_to_idx[fx] for fx in var_set} @@ -1004,7 +1004,7 @@ def save_fvars(self, filename): sage: filename = f.get_fr_str() + "_solver_results.pickle" sage: f.save_fvars(filename) sage: del f - sage: f2 = FMatrix(FusionRing("A2",1)) + sage: f2 = FMatrix(FusionRing("A2", 1)) sage: f2.load_fvars(filename) sage: fvars == f2.get_fvars() True @@ -1029,14 +1029,14 @@ def load_fvars(self, filename): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2",1)) + sage: f = FMatrix(FusionRing("A2", 1)) sage: f.find_orthogonal_solution(verbose=False) sage: fvars = f.get_fvars() sage: K = f.field() sage: filename = f.get_fr_str() + "_solver_results.pickle" sage: f.save_fvars(filename) sage: del f - sage: f2 = FMatrix(FusionRing("A2",1)) + sage: f2 = FMatrix(FusionRing("A2", 1)) sage: f2.load_fvars(filename) sage: fvars == f2.get_fvars() True @@ -1081,19 +1081,19 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f = FMatrix(FusionRing("A1", 3)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: f.get_defining_equations('hexagons',output=False) + sage: f.get_defining_equations('hexagons', output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: f._fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params() - sage: f._checkpoint(do_chkpt=True,status=2) + sage: f._checkpoint(do_chkpt=True, status=2) Checkpoint 2 reached! sage: del f - sage: f = FMatrix(FusionRing("A1",3)) + sage: f = FMatrix(FusionRing("A1", 3)) sage: f.find_orthogonal_solution(warm_start="fmatrix_solver_checkpoint_A13.pickle") Computing F-symbols for The Fusion Ring of Type A1 and level 3 with Integer Ring coefficients with 71 variables... Set up 121 reduced pentagons... @@ -1111,10 +1111,10 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: sum(f._solved) == f._poly_ring.ngens() True sage: os.remove("fmatrix_solver_checkpoint_A13.pickle") - sage: f = FMatrix(FusionRing("A1",2)) + sage: f = FMatrix(FusionRing("A1", 2)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) - sage: f.get_defining_equations('hexagons',output=False) + sage: f.get_defining_equations('hexagons', output=False) sage: f.ideal_basis = f._par_graph_gb(verbose=False) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey sage: f.ideal_basis.sort(key=poly_tup_sortkey) @@ -1123,10 +1123,10 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f._fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: f._triangular_elim(verbose=False) sage: f._update_reduction_params() - sage: f.get_defining_equations('pentagons',output=False) + sage: f.get_defining_equations('pentagons', output=False) sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: f._triangular_elim(verbose=False) - sage: f._checkpoint(do_chkpt=True,status=4) + sage: f._checkpoint(do_chkpt=True, status=4) Checkpoint 4 reached! sage: del f sage: f = FMatrix(FusionRing("A1", 2)) @@ -1196,7 +1196,7 @@ def _restore_state(self, filename): TESTS:: sage: f = FMatrix(FusionRing("A1", 3)) - sage: f.find_orthogonal_solution(save_results="test.pickle",verbose=False) # long time + sage: f.find_orthogonal_solution(save_results="test.pickle", verbose=False) # long time sage: del f sage: f = FMatrix(FusionRing("A1", 3)) sage: f.find_orthogonal_solution(warm_start="test.pickle") # long time @@ -1235,7 +1235,7 @@ def start_worker_pool(self, processes=None): .. NOTE:: Python 3.8+ is required, since the ``multiprocessing.shared_memory`` - module must be imported. + module must be imported. INPUT: @@ -1290,9 +1290,9 @@ class methods. self._var_degs = shared_memory.ShareableList(self._var_degs) vd_name = self._var_degs.shm.name n = self._poly_ring.ngens() - self._ks = KSHandler(n,self._field,use_mp=True,init_data=self._ks) + self._ks = KSHandler(n, self._field, use_mp=True, init_data=self._ks) ks_names = self._ks.shm.name - self._shared_fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name,init_data=self._fvars) + self._shared_fvars = FvarsHandler(n, self._field, self._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name, init_data=self._fvars) fvar_names = self._shared_fvars.shm.name #Initialize worker pool processes args = (id(self), s_name, vd_name, ks_names, fvar_names, n_proc, pids_name) @@ -1305,10 +1305,10 @@ def init(fmats_id, solved_name, vd_name, ks_names, fvar_names, n_proc, pids_name fmats_obj._var_degs = shared_memory.ShareableList(name=vd_name) n = fmats_obj._poly_ring.ngens() K = fmats_obj._field - fmats_obj._fvars = FvarsHandler(n,K,fmats_obj._idx_to_sextuple,name=fvar_names,use_mp=n_proc,pids_name=pids_name) - fmats_obj._ks = KSHandler(n,K,name=ks_names,use_mp=True) + fmats_obj._fvars = FvarsHandler(n, K, fmats_obj._idx_to_sextuple, name=fvar_names, use_mp=n_proc, pids_name=pids_name) + fmats_obj._ks = KSHandler(n, K, name=ks_names, use_mp=True) - self.pool = Pool(processes=n_proc,initializer=init,initargs=args) + self.pool = Pool(processes=n_proc, initializer=init, initargs=args) self._pid_list[0] = getpid() for i, p in enumerate(self.pool._pool): self._pid_list[i+1] = p.pid @@ -1367,11 +1367,11 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, sage: f = FMatrix(FusionRing("A1", 2)) sage: f._reset_solver_state() - sage: len(f._map_triv_reduce('get_reduced_hexagons',[(0,1,False)])) + sage: len(f._map_triv_reduce('get_reduced_hexagons', [(0, 1, False)])) 11 sage: f.start_worker_pool() - sage: mp_params = [(i,f.pool._processes,True) for i in range(f.pool._processes)] - sage: len(f._map_triv_reduce('get_reduced_pentagons',mp_params,worker_pool=f.pool,chunksize=1,mp_thresh=0)) + sage: mp_params = [(i, f.pool._processes, True) for i in range(f.pool._processes)] + sage: len(f._map_triv_reduce('get_reduced_pentagons', mp_params, worker_pool=f.pool, chunksize=1, mp_thresh=0)) 33 sage: f.shutdown_worker_pool() """ @@ -1387,11 +1387,11 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, chunksize = n // (worker_pool._processes**2) + 1 no_mp = worker_pool is None or n < mp_thresh #Map phase - input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) + input_iter = zip_longest([], input_iter, fillvalue=(mapper, id(self))) if no_mp: - mapped = map(executor,input_iter) + mapped = map(executor, input_iter) else: - mapped = worker_pool.imap_unordered(executor,input_iter,chunksize=chunksize) + mapped = worker_pool.imap_unordered(executor, input_iter, chunksize=chunksize) #Reduce phase results = set() for child_eqns in mapped: @@ -1501,7 +1501,7 @@ def get_defining_equations(self, option, output=True): self._reset_solver_state() n_proc = self.pool._processes if self.pool is not None else 1 params = [(child_id, n_proc, output) for child_id in range(n_proc)] - eqns = self._map_triv_reduce('get_reduced_'+option,params,worker_pool=self.pool,chunksize=1,mp_thresh=0) + eqns = self._map_triv_reduce('get_reduced_'+option, params, worker_pool=self.pool, chunksize=1, mp_thresh=0) if output: F = self._field for i, eq_tup in enumerate(eqns): @@ -1536,7 +1536,7 @@ def _tup_to_fpoly(self, eq_tup): True sage: f.shutdown_worker_pool() """ - return _tup_to_poly(eq_tup,parent=self._poly_ring) + return _tup_to_poly(eq_tup, parent=self._poly_ring) def _update_reduction_params(self, eqns=None): r""" @@ -1562,10 +1562,10 @@ def _update_reduction_params(self, eqns=None): if eqns is None: eqns = self.ideal_basis self._ks.update(eqns) - for i, d in enumerate(get_variables_degrees(eqns,self._poly_ring.ngens())): + for i, d in enumerate(get_variables_degrees(eqns, self._poly_ring.ngens())): self._var_degs[i] = d self._nnz = self._get_known_nonz() - self._kp = compute_known_powers(self._var_degs,self._get_known_vals(),self._field.one()) + self._kp = compute_known_powers(self._var_degs, self._get_known_vals(), self._field.one()) def _triangular_elim(self, eqns=None, verbose=True): r""" @@ -1597,7 +1597,7 @@ def _triangular_elim(self, eqns=None, verbose=True): eqns = self.ideal_basis while True: - linear_terms_exist = _solve_for_linear_terms(self,eqns) + linear_terms_exist = _solve_for_linear_terms(self, eqns) if not linear_terms_exist: break _backward_subs(self) @@ -1611,7 +1611,7 @@ def _triangular_elim(self, eqns=None, verbose=True): eqns = chunks else: eqns = [eqns] - eqns = self._map_triv_reduce('update_reduce',eqns,worker_pool=self.pool,mp_thresh=0) + eqns = self._map_triv_reduce('update_reduce', eqns, worker_pool=self.pool, mp_thresh=0) eqns.sort(key=poly_tup_sortkey) if verbose: print("Elimination epoch completed... {} eqns remain in ideal basis".format(len(eqns))) @@ -1687,7 +1687,7 @@ def equations_graph(self, eqns=None): for x in s: for y in s: if y!=x: - G.add_edge(x,y) + G.add_edge(x, y) return G def _partition_eqns(self, eqns=None, verbose=True): @@ -1703,8 +1703,8 @@ def _partition_eqns(self, eqns=None, verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("C2",1)) - sage: f.get_defining_equations('hexagons',output=False) + sage: f = FMatrix(FusionRing("C2", 1)) + sage: f.get_defining_equations('hexagons', output=False) sage: partition = f._partition_eqns() Partitioned 11 equations into 5 components of size: [4, 3, 3, 3, 1] @@ -1721,7 +1721,7 @@ def _partition_eqns(self, eqns=None, verbose=True): sage: eqns_in_partition == set(f.ideal_basis) True sage: from itertools import product - sage: for e1, e2 in product(partition.values(),repeat=2): + sage: for e1, e2 in product(partition.values(), repeat=2): ....: assert e1 == e2 or set(e1).isdisjoint(set(e2)) """ if eqns is None: @@ -1731,7 +1731,7 @@ def _partition_eqns(self, eqns=None, verbose=True): for eq_tup in eqns: partition[tuple(graph.connected_component_containing_vertex(variables(eq_tup)[0]))].append(eq_tup) if verbose: - print("Partitioned {} equations into {} components of size:".format(len(eqns),len(graph.connected_components()))) + print("Partitioned {} equations into {} components of size:".format(len(eqns), len(graph.connected_components()))) print(graph.connected_components_sizes()) return partition @@ -1751,11 +1751,11 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb EXAMPLES:: - sage: f = FMatrix(FusionRing("F4",1)) + sage: f = FMatrix(FusionRing("F4", 1)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.start_worker_pool() - sage: f.get_defining_equations('hexagons',output=False) + sage: f.get_defining_equations('hexagons', output=False) sage: gb = f._par_graph_gb() Partitioned 10 equations into 2 components of size: [4, 1] @@ -1772,7 +1772,7 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb if eqns is None: eqns = self.ideal_basis small_comps = list() temp_eqns = list() - for comp, comp_eqns in self._partition_eqns(eqns=eqns,verbose=verbose).items(): + for comp, comp_eqns in self._partition_eqns(eqns=eqns, verbose=verbose).items(): #Check if component is too large to process if len(comp) > largest_comp: temp_eqns.extend(comp_eqns) @@ -1783,7 +1783,7 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb ret = small_comp_gb + temp_eqns return ret - def _get_component_variety(self,var,eqns): + def _get_component_variety(self, var, eqns): r""" Translate equations in each connected component to smaller polynomial rings so we can call built-in variety method. @@ -1816,8 +1816,8 @@ def _get_component_variety(self,var,eqns): #Zip tuples into R and compute Groebner basis idx_map = {old: new for new, old in enumerate(sorted(var))} nvars = len(var) - eqns = [_unflatten_coeffs(self._field,eq_tup) for eq_tup in eqns] - polys = [_tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R) for eq_tup in eqns] + eqns = [_unflatten_coeffs(self._field, eq_tup) for eq_tup in eqns] + polys = [_tup_to_poly(resize(eq_tup, idx_map, nvars), parent=R) for eq_tup in eqns] var_in_R = Ideal(sorted(polys)).variety(ring=AA) #Change back to fmats poly ring and append to temp_eqns @@ -1884,7 +1884,7 @@ def attempt_number_field_computation(self): return False return True - def _get_explicit_solution(self,eqns=None,verbose=True): + def _get_explicit_solution(self, eqns=None, verbose=True): r""" Construct an explicit solution of ``self``. @@ -1923,8 +1923,8 @@ def _get_explicit_solution(self,eqns=None,verbose=True): one = self._field.one() for fx, rhs in self._ks.items(): if not self._solved[fx]: - lt = (ETuple({fx : 2},n), one) - eqns.append(((lt, (ETuple({},n), -rhs)))) + lt = (ETuple({fx : 2}, n), one) + eqns.append(((lt, (ETuple({}, n), -rhs)))) eqns_partition = self._partition_eqns(verbose=verbose) F = self._field @@ -1932,27 +1932,27 @@ def _get_explicit_solution(self,eqns=None,verbose=True): numeric_fvars = dict() non_cyclotomic_roots = list() must_change_base_field = False - phi = F.hom([F.gen()],F) + phi = F.hom([F.gen()], F) for comp, part in eqns_partition.items(): #If component has only one equation in a single variable, get a root if len(comp) == 1 and len(part) == 1: #Attempt to find cyclotomic root - univ_poly = tup_to_univ_poly(part[0],R) + univ_poly = tup_to_univ_poly(part[0], R) roots = univ_poly.roots(multiplicities=False) if roots: numeric_fvars[comp[0]] = roots[0] else: #A real solution is preferred - roots = univ_poly.roots(ring=AA,multiplicities=False) + roots = univ_poly.roots(ring=AA, multiplicities=False) if not roots: - roots = univ_poly.roots(ring=QQbar,multiplicities=False) - non_cyclotomic_roots.append((comp[0],roots[0])) + roots = univ_poly.roots(ring=QQbar, multiplicities=False) + non_cyclotomic_roots.append((comp[0], roots[0])) must_change_base_field = True #Otherwise, compute the component variety and select a point to obtain a numerical solution else: - sols = self._get_component_variety(comp,part) + sols = self._get_component_variety(comp, part) for fx, rhs in sols[0].items(): - non_cyclotomic_roots.append((fx,rhs)) + non_cyclotomic_roots.append((fx, rhs)) must_change_base_field = True if must_change_base_field: @@ -1962,11 +1962,11 @@ def _get_explicit_solution(self,eqns=None,verbose=True): if verbose: print("Computing appropriate NumberField...") roots = [self._FR.field().gen()]+[r[1] for r in non_cyclotomic_roots] - self._field, bf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots,minimal=True) + self._field, bf_elts, self._qqbar_embedding = number_field_elements_from_algebraics(roots, minimal=True) else: self._field = QQbar bf_elts = [self._qqbar_embedding(F.gen())] - bf_elts += [rhs for fx,rhs in non_cyclotomic_roots] + bf_elts += [rhs for fx, rhs in non_cyclotomic_roots] self._qqbar_embedding = lambda x : x self._non_cyc_roots = bf_elts[1:] @@ -1987,11 +1987,11 @@ def _get_explicit_solution(self,eqns=None,verbose=True): assert sum(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in range(nvars) if not self._solved[fx]]) #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) - self._fvars = {sextuple : apply_coeff_map(rhs,phi) for sextuple, rhs in self._fvars.items()} + self._fvars = {sextuple : apply_coeff_map(rhs, phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): - self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({},nvars),rhs),) - _backward_subs(self,flatten=False) - self._fvars = {sextuple : constant_coeff(rhs,self._field) for sextuple, rhs in self._fvars.items()} + self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({}, nvars), rhs), ) + _backward_subs(self, flatten=False) + self._fvars = {sextuple : constant_coeff(rhs, self._field) for sextuple, rhs in self._fvars.items()} #Update base field attributes self._FR._field = self.field() @@ -2075,7 +2075,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start sage: f.fmat(b2, b2, b2, b2, b0, b1) -1/2*zeta80^30 + 1/2*zeta80^10 - Every F-matrix `F^{a,b,c}_d` is orthogonal and in many cases real. + Every F-matrix `F^{a, b, c}_d` is orthogonal and in many cases real. We may use :meth:`fmats_are_orthogonal` and :meth:`fvars_are_real` to obtain correctness certificates. @@ -2116,7 +2116,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start if self._chkpt_status < 1: #Set up hexagon equations and orthogonality constraints self.get_orthogonality_constraints(output=False) - self.get_defining_equations('hexagons',output=False) + self.get_defining_equations('hexagons', output=False) #Report progress if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) @@ -2126,8 +2126,8 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self._fvars = self._shared_fvars else: n = self._poly_ring.ngens() - self._fvars = FvarsHandler(n,self._field,self._idx_to_sextuple,init_data=self._fvars) - self._checkpoint(checkpoint,1,verbose=verbose) + self._fvars = FvarsHandler(n, self._field, self._idx_to_sextuple, init_data=self._fvars) + self._checkpoint(checkpoint, 1, verbose=verbose) if self._chkpt_status < 2: #Set up equations graph. Find GB for each component in parallel. Eliminate variables @@ -2137,15 +2137,15 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: print("Hex elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) - self._checkpoint(checkpoint,2,verbose=verbose) + self._checkpoint(checkpoint, 2, verbose=verbose) if self._chkpt_status < 3: #Set up pentagon equations in parallel - self.get_defining_equations('pentagons',output=False) + self.get_defining_equations('pentagons', output=False) #Report progress if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) - self._checkpoint(checkpoint,3,verbose=verbose) + self._checkpoint(checkpoint, 3, verbose=verbose) if self._chkpt_status < 4: #Simplify and eliminate variables @@ -2154,16 +2154,16 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start #Report progress if verbose: print("Pent elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) - self._checkpoint(checkpoint,4,verbose=verbose) + self._checkpoint(checkpoint, 4, verbose=verbose) #Try adding degrevlex gb -> elim loop until len(ideal_basis) does not change #Set up new equations graph and compute variety for each component if self._chkpt_status < 5: - self.ideal_basis = self._par_graph_gb(term_order="lex",verbose=verbose) + self.ideal_basis = self._par_graph_gb(term_order="lex", verbose=verbose) self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(verbose=verbose) - self._checkpoint(checkpoint,5,verbose=verbose) + self._checkpoint(checkpoint, 5, verbose=verbose) self.shutdown_worker_pool() #Find numeric values for each F-symbol @@ -2375,8 +2375,8 @@ def fmats_are_orthogonal(self): True """ is_orthog = [] - for a,b,c,d in product(self._FR.basis(),repeat=4): - mat = self.fmatrix(a,b,c,d) + for a, b, c, d in product(self._FR.basis(), repeat=4): + mat = self.fmatrix(a, b, c, d) is_orthog.append(mat.T * mat == matrix.identity(mat.nrows())) return all(is_orthog) @@ -2395,11 +2395,11 @@ def fvars_are_real(self): for k, v in self._fvars.items(): AA(self._qqbar_embedding(v)) except ValueError: - print("the F-symbol {} (key {}) has a nonzero imaginary part".format(v,k)) + print("the F-symbol {} (key {}) has a nonzero imaginary part".format(v, k)) return False return True - def certify_pentagons(self,use_mp=True, verbose=False): + def certify_pentagons(self, use_mp=True, verbose=False): r""" Obtain a certificate of satisfaction for the pentagon equations, up to floating-point error. @@ -2446,7 +2446,7 @@ def certify_pentagons(self,use_mp=True, verbose=False): n_proc = pool._processes if pool is not None else 1 params = [(child_id, n_proc, verbose) for child_id in range(n_proc)] pe = self._map_triv_reduce('pent_verify', params, worker_pool=pool, chunksize=1, mp_thresh=0) - if np.all(np.isclose(np.array(pe),0,atol=1e-7)): + if np.all(np.isclose(np.array(pe), 0, atol=1e-7)): if verbose: print("Found valid F-symbols for {}".format(self._FR)) pe = None diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx index c3158585904..98983f0e171 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx @@ -10,12 +10,12 @@ Fast F-Matrix methods cimport cython from sage.algebras.fusion_rings.poly_tup_engine cimport ( - compute_known_powers, - get_variables_degrees, variables, - poly_to_tup, _tup_to_poly, - subs, subs_squares, reduce_poly_dict, resize, - _flatten_coeffs, _unflatten_coeffs, - has_appropriate_linear_term, + compute_known_powers, + get_variables_degrees, variables, + poly_to_tup, _tup_to_poly, + subs, subs_squares, reduce_poly_dict, resize, + _flatten_coeffs, _unflatten_coeffs, + has_appropriate_linear_term, resize ) from sage.algebras.fusion_rings.shm_managers cimport KSHandler, FvarsHandler @@ -39,7 +39,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -50,7 +50,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: f._fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import _solve_for_linear_terms sage: _solve_for_linear_terms(f) True @@ -84,7 +84,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): #TESTS: # s = factory._idx_to_sextuple[vars[0]] # factory.test_fvars[s] = tuple() - # assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s],factory.test_fvars[s]) + # assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s], factory.test_fvars[s]) if len(eq_tup) == 2: idx = has_appropriate_linear_term(eq_tup) if idx < 0: continue @@ -96,14 +96,14 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): coeff = factory._field(list(eq_tup[(idx+1) % 2][1])) other = factory._field(list(eq_tup[idx][1])) rhs_coeff = tuple((-coeff / other)._coefficients()) - fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp,rhs_coeff),) + fvars[factory._idx_to_sextuple[max_var]] = ((rhs_exp, rhs_coeff), ) factory._solved[max_var] = True linear_terms_exist = True #TESTS: # s = factory._idx_to_sextuple[max_var] - # factory.test_fvars[s] = ((rhs_exp,rhs_coeff),) - # assert _unflatten_coeffs(factory._field,factory.test_fvars[s]) == fvars[s], "OG value {}, Shared: {}".format(factory.test_fvars[s],fvars[s]) + # factory.test_fvars[s] = ((rhs_exp, rhs_coeff), ) + # assert _unflatten_coeffs(factory._field, factory.test_fvars[s]) == fvars[s], "OG value {}, Shared: {}".format(factory.test_fvars[s], fvars[s]) return linear_terms_exist cpdef _backward_subs(factory, bint flatten=True): @@ -113,7 +113,7 @@ cpdef _backward_subs(factory, bint flatten=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3",1), inject_variables=True) + sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -124,7 +124,7 @@ cpdef _backward_subs(factory, bint flatten=True): sage: f.ideal_basis.sort(key=poly_tup_sortkey) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() - sage: f._fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: f._fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import _solve_for_linear_terms sage: _solve_for_linear_terms(f) True @@ -157,14 +157,14 @@ cpdef _backward_subs(factory, bint flatten=True): cdef KSHandler _ks = factory._ks cdef dict idx_to_sextuple = factory._idx_to_sextuple cdef int nvars = len(idx_to_sextuple) - for i in range(nvars-1,-1,-1): + for i in range(nvars-1, -1, -1): sextuple = idx_to_sextuple[i] rhs = fvars[sextuple] d = {var_idx: fvars[idx_to_sextuple[var_idx]] for var_idx in variables(rhs) if solved[var_idx]} if d: - kp = compute_known_powers(get_variables_degrees([rhs],nvars), d, one) - res = tuple(subs_squares(subs(rhs,kp,one),_ks).items()) + kp = compute_known_powers(get_variables_degrees([rhs], nvars), d, one) + res = tuple(subs_squares(subs(rhs, kp, one), _ks).items()) if flatten: res = _flatten_coeffs(res) fvars[sextuple] = res @@ -173,7 +173,7 @@ cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): """ Cython version of fmat class method. Using cdef for fastest dispatch """ - if _Nk_ij(a,b,x) == 0 or _Nk_ij(x,c,d) == 0 or _Nk_ij(b,c,y) == 0 or _Nk_ij(a,y,d) == 0: + if _Nk_ij(a, b, x) == 0 or _Nk_ij(x, c, d) == 0 or _Nk_ij(b, c, y) == 0 or _Nk_ij(a, y, d) == 0: return 0 #Some known F-symbols if a == id_anyon: @@ -191,7 +191,7 @@ cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): return 1 else: return 0 - return fvars[a,b,c,d,x,y] + return fvars[a, b, c, d, x, y] ###################################### ### Fast fusion coefficients cache ### @@ -200,16 +200,16 @@ cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): # from sage.misc.cachefunc import cached_function # cdef dict _Nk_ij = dict() -# cpdef _Nk_ij(factory,proc): +# cpdef _Nk_ij(factory, proc): # cdef int coeff -# for a,b,c in product(factory._FR.basis(),repeat=3): +# for a, b, c in product(factory._FR.basis(), repeat=3): # try: # coeff = (a*b).monomial_coefficients(copy=False)[c.weight()] # except: # coeff = 0 -# _Nk_ij[a,b,c] = coeff +# _Nk_ij[a, b, c] = coeff -# cpdef int _Nk_ij(a,b,c): +# cpdef int _Nk_ij(a, b, c): # try: # return (a*b).monomial_coefficients(copy=False)[c.weight()] # except KeyError: @@ -229,10 +229,10 @@ cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): a, b, c, d, e, g = sextuple #To add typing we need to ensure all fmats.fmat are of the same type? #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? - lhs = r_matrix(a,c,e,base_coercion=False) * _fmat(fvars,Nk_ij,id_anyon,a,c,b,d,e,g) * r_matrix(b,c,g,base_coercion=False) + lhs = r_matrix(a, c, e, base_coercion=False) * _fmat(fvars, Nk_ij, id_anyon, a, c, b, d, e, g) * r_matrix(b, c, g, base_coercion=False) rhs = 0 for f in basis: - rhs += _fmat(fvars,Nk_ij,id_anyon,c,a,b,d,e,f) * r_matrix(f,c,d,base_coercion=False) * _fmat(fvars,Nk_ij,id_anyon,a,b,c,d,f,g) + rhs += _fmat(fvars, Nk_ij, id_anyon, c, a, b, d, e, f) * r_matrix(f, c, d, base_coercion=False) * _fmat(fvars, Nk_ij, id_anyon, a, b, c, d, f, g) return lhs-rhs @cython.wraparound(False) @@ -261,7 +261,7 @@ cdef get_reduced_hexagons(factory, tuple mp_params): must_zip_up = isinstance(v, tuple) break if must_zip_up: - fvars = {k: _tup_to_poly(v,parent=factory._poly_ring) for k, v in factory._fvars.items()} + fvars = {k: _tup_to_poly(v, parent=factory._poly_ring) for k, v in factory._fvars.items()} else: fvars = factory._fvars r_matrix = factory._FR.r_matrix @@ -277,9 +277,9 @@ cdef get_reduced_hexagons(factory, tuple mp_params): for i in range(len(basis)**6): sextuple = next(it) if i % n_proc == child_id: - he = req_cy(basis,r_matrix,fvars,_Nk_ij,id_anyon,sextuple) + he = req_cy(basis, r_matrix, fvars, _Nk_ij, id_anyon, sextuple) if he: - red = reduce_poly_dict(he.dict(),_nnz,_ks,one) + red = reduce_poly_dict(he.dict(), _nnz, _ks, one) #Avoid pickling cyclotomic coefficients red = _flatten_coeffs(red) @@ -294,12 +294,12 @@ cdef MPolynomial_libsingular feq_cy(tuple basis, fvars, Nk_ij, id_anyon, zero, t as a polynomial object. """ a, b, c, d, e, f, g, k, l = nonuple - lhs = _fmat(fvars,Nk_ij,id_anyon,f,c,d,e,g,l) * _fmat(fvars,Nk_ij,id_anyon,a,b,l,e,f,k) + lhs = _fmat(fvars, Nk_ij, id_anyon, f, c, d, e, g, l) * _fmat(fvars, Nk_ij, id_anyon, a, b, l, e, f, k) if lhs == 0 and prune: # it is believed that if lhs=0, the equation carries no new information return zero rhs = zero for h in basis: - rhs += _fmat(fvars,Nk_ij,id_anyon,a,b,c,g,f,h)*_fmat(fvars,Nk_ij,id_anyon,a,h,d,e,g,k)*_fmat(fvars,Nk_ij,id_anyon,b,c,d,k,h,l) + rhs += _fmat(fvars, Nk_ij, id_anyon, a, b, c, g, f, h)*_fmat(fvars, Nk_ij, id_anyon, a, h, d, e, g, k)*_fmat(fvars, Nk_ij, id_anyon, b, c, d, k, h, l) return lhs - rhs @cython.wraparound(False) @@ -326,7 +326,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): break cdef dict fvars if must_zip_up: - fvars = {k: _tup_to_poly(v,parent=factory._poly_ring) for k, v in factory._fvars.items()} + fvars = {k: _tup_to_poly(v, parent=factory._poly_ring) for k, v in factory._fvars.items()} else: fvars = factory._fvars _Nk_ij = factory._FR.Nk_ij @@ -339,7 +339,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): cdef ETuple _nnz = factory._nnz #Computation loop - it = product(basis,repeat=9) + it = product(basis, repeat=9) for i in range(len(basis)**9): nonuple = next(it) if i % n_proc == child_id: @@ -367,7 +367,7 @@ cdef list update_reduce(factory, list eqns): cdef KSHandler _ks = factory._ks #Update reduction params factory._nnz = factory._get_known_nonz() - factory._kp = compute_known_powers(factory._var_degs,factory._get_known_vals(),factory._field.one()) + factory._kp = compute_known_powers(factory._var_degs, factory._get_known_vals(), factory._field.one()) cdef dict _kp = factory._kp cdef ETuple _nnz = factory._nnz @@ -376,8 +376,8 @@ cdef list update_reduce(factory, list eqns): #Construct cyclotomic field elts from list repn unflat = _unflatten_coeffs(_field, eq_tup) - eq_dict = subs(unflat,_kp,one) - red = reduce_poly_dict(eq_dict,_nnz,_ks,one) + eq_dict = subs(unflat, _kp, one) + red = reduce_poly_dict(eq_dict, _nnz, _ks, one) #Avoid pickling cyclotomic coefficients red = _flatten_coeffs(red) @@ -400,7 +400,7 @@ cdef list compute_gb(factory, tuple args): for fx in variables(eq_tup): sorted_vars.append(fx) sorted_vars = sorted(set(sorted_vars)) - cdef MPolynomialRing_libsingular R = PolynomialRing(factory._FR.field(),len(sorted_vars),'a',order=term_order) + cdef MPolynomialRing_libsingular R = PolynomialRing(factory._FR.field(), len(sorted_vars), 'a', order=term_order) #Zip tuples into R and compute Groebner basis cdef idx_map = { old : new for new, old in enumerate(sorted_vars) } @@ -409,7 +409,7 @@ cdef list compute_gb(factory, tuple args): cdef list polys = list() for eq_tup in eqns: eq_tup = _unflatten_coeffs(F, eq_tup) - polys.append(_tup_to_poly(resize(eq_tup,idx_map,nvars),parent=R)) + polys.append(_tup_to_poly(resize(eq_tup, idx_map, nvars), parent=R)) gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") #Change back to fmats poly ring and append to temp_eqns @@ -417,7 +417,7 @@ cdef list compute_gb(factory, tuple args): cdef tuple t nvars = factory._poly_ring.ngens() for p in gb: - t = resize(poly_to_tup(p),inv_idx_map,nvars) + t = resize(poly_to_tup(p), inv_idx_map, nvars) #Avoid pickling cyclotomic coefficients t = _flatten_coeffs(t) @@ -447,10 +447,10 @@ cdef inline list collect_eqns(list eqns): #Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { - "get_reduced_hexagons": get_reduced_hexagons, - "get_reduced_pentagons": get_reduced_pentagons, - "update_reduce": update_reduce, - "compute_gb": compute_gb, + "get_reduced_hexagons": get_reduced_hexagons, + "get_reduced_pentagons": get_reduced_pentagons, + "update_reduce": update_reduce, + "compute_gb": compute_gb, "pent_verify": pent_verify } @@ -480,14 +480,14 @@ cpdef executor(tuple params): TESTS:: sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import executor - sage: fmats = FMatrix(FusionRing("A1",3)) + sage: fmats = FMatrix(FusionRing("A1", 3)) sage: fmats._reset_solver_state() - sage: params = (('get_reduced_hexagons', id(fmats)), (0,1,True)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0, 1, True)) sage: len(executor(params)) == 63 True - sage: fmats = FMatrix(FusionRing("E6",1)) + sage: fmats = FMatrix(FusionRing("E6", 1)) sage: fmats._reset_solver_state() - sage: params = (('get_reduced_hexagons', id(fmats)), (0,1,False)) + sage: params = (('get_reduced_hexagons', id(fmats)), (0, 1, False)) sage: len(executor(params)) == 6 True """ @@ -495,7 +495,7 @@ cpdef executor(tuple params): #Construct a reference to global FMatrix object in this worker's memory fmats_obj = cast(fmats_id, py_object).value #Bind module method to FMatrix object in worker process, and call the method - return mappers[fn_name](fmats_obj,args) + return mappers[fn_name](fmats_obj, args) #################### ### Verification ### @@ -508,10 +508,10 @@ cdef feq_verif(factory, worker_results, fvars, Nk_ij, id_anyon, tuple nonuple, f a, b, c, d, e, f, g, k, l = nonuple cdef float diff, lhs, rhs - lhs = _fmat(fvars,Nk_ij,id_anyon,f,c,d,e,g,l)*_fmat(fvars,Nk_ij,id_anyon,a,b,l,e,f,k) + lhs = _fmat(fvars, Nk_ij, id_anyon, f, c, d, e, g, l)*_fmat(fvars, Nk_ij, id_anyon, a, b, l, e, f, k) rhs = 0.0 for h in factory._FR.basis(): - rhs += _fmat(fvars,Nk_ij,id_anyon,a,b,c,g,f,h)*_fmat(fvars,Nk_ij,id_anyon,a,h,d,e,g,k)*_fmat(fvars,Nk_ij,id_anyon,b,c,d,k,h,l) + rhs += _fmat(fvars, Nk_ij, id_anyon, a, b, c, g, f, h)*_fmat(fvars, Nk_ij, id_anyon, a, h, d, e, g, k)*_fmat(fvars, Nk_ij, id_anyon, b, c, d, k, h, l) diff = lhs - rhs if diff > tol or diff < -tol: worker_results.append(diff) @@ -521,7 +521,7 @@ cdef feq_verif(factory, worker_results, fvars, Nk_ij, id_anyon, tuple nonuple, f @cython.cdivision(True) cdef pent_verify(factory, tuple mp_params): r""" - Generate all the pentagon equations assigned to this process, + Generate all the pentagon equations assigned to this process, and reduce them. """ child_id, n_proc, verbose = mp_params @@ -536,7 +536,7 @@ cdef pent_verify(factory, tuple mp_params): id_anyon = factory._FR.one() for i, nonuple in enumerate(product(factory._FR.basis(), repeat=9)): if i % n_proc == child_id: - feq_verif(factory,worker_results,fvars,Nk_ij,id_anyon,nonuple) + feq_verif(factory, worker_results, fvars, Nk_ij, id_anyon, nonuple) if i % 50000000 == 0 and i and verbose: - print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000,len(worker_results))) + print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000, len(worker_results))) diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx index 49af0008602..7d60bdb0d6e 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx @@ -18,9 +18,9 @@ from sage.rings.qqbar import QQbar ### Mappers ### ############### -cdef mid_sig_ij(fusion_ring,row,col,a,b): +cdef mid_sig_ij(fusion_ring, row, col, a, b): r""" - Compute the (xi,yi), (xj,yj) entry of generator braiding the middle two + Compute the (xi, yi), (xj, yj) entry of generator braiding the middle two strands in the tree b -> xi # yi -> (a # a) # (a # a), which results in a sum over j of trees b -> xj # yj -> (a # a) # (a # a) @@ -40,15 +40,15 @@ cdef mid_sig_ij(fusion_ring,row,col,a,b): for c in basis: for d in basis: ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) - f1 = _fmat(_fvars,_Nk_ij,one,a,a,yi,b,xi,c) - f2 = _fmat(_fvars,_Nk_ij,one,a,a,a,c,d,yi) - f3 = _fmat(_fvars,_Nk_ij,one,a,a,a,c,d,yj) - f4 = _fmat(_fvars,_Nk_ij,one,a,a,yj,b,xj,c) - r = fusion_ring.r_matrix(a,a,d) + f1 = _fmat(_fvars, _Nk_ij, one, a, a, yi, b, xi, c) + f2 = _fmat(_fvars, _Nk_ij, one, a, a, a, c, d, yi) + f3 = _fmat(_fvars, _Nk_ij, one, a, a, a, c, d, yj) + f4 = _fmat(_fvars, _Nk_ij, one, a, a, yj, b, xj, c) + r = fusion_ring.r_matrix(a, a, d) entry += f1 * f2 * r * f3 * f4 return entry -cdef odd_one_out_ij(fusion_ring,xi,xj,a,b): +cdef odd_one_out_ij(fusion_ring, xi, xj, a, b): r""" Compute the `xi`, `xj` entry of the braid generator on the two right-most strands, corresponding to the tree b -> (xi # a) -> (a # a) # a, which @@ -66,9 +66,9 @@ cdef odd_one_out_ij(fusion_ring,xi,xj,a,b): entry = 0 for c in fusion_ring.basis(): ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) - f1 = _fmat(_fvars,_Nk_ij,one,a,a,a,b,xi,c) - f2 = _fmat(_fvars,_Nk_ij,one,a,a,a,b,xj,c) - r = fusion_ring.r_matrix(a,a,c) + f1 = _fmat(_fvars, _Nk_ij, one, a, a, a, b, xi, c) + f2 = _fmat(_fvars, _Nk_ij, one, a, a, a, b, xj, c) + r = fusion_ring.r_matrix(a, a, c) entry += f1 * r * f2 return entry @@ -76,24 +76,24 @@ cdef odd_one_out_ij(fusion_ring,xi,xj,a,b): cdef odd_one_out_ij_cache = dict() cdef mid_sig_ij_cache = dict() -cdef cached_mid_sig_ij(fusion_ring,row,col,a,b): +cdef cached_mid_sig_ij(fusion_ring, row, col, a, b): r""" Cached version of :meth:`mid_sig_ij`. """ - if (row,col,a,b) in mid_sig_ij_cache: - return mid_sig_ij_cache[row,col,a,b] - entry = mid_sig_ij(fusion_ring,row,col,a,b) - mid_sig_ij_cache[row,col,a,b] = entry + if (row, col, a, b) in mid_sig_ij_cache: + return mid_sig_ij_cache[row, col, a, b] + entry = mid_sig_ij(fusion_ring, row, col, a, b) + mid_sig_ij_cache[row, col, a, b] = entry return entry -cdef cached_odd_one_out_ij(fusion_ring,xi,xj,a,b): +cdef cached_odd_one_out_ij(fusion_ring, xi, xj, a, b): r""" Cached version of :meth:`odd_one_out_ij`. """ - if (xi,xj,a,b) in odd_one_out_ij_cache: - return odd_one_out_ij_cache[xi,xj,a,b] - entry = odd_one_out_ij(fusion_ring,xi,xj,a,b) - odd_one_out_ij_cache[xi,xj,a,b] = entry + if (xi, xj, a, b) in odd_one_out_ij_cache: + return odd_one_out_ij_cache[xi, xj, a, b] + entry = odd_one_out_ij(fusion_ring, xi, xj, a, b) + odd_one_out_ij_cache[xi, xj, a, b] = entry return entry @cython.nonecheck(False) @@ -113,7 +113,7 @@ cdef sig_2k(fusion_ring, tuple args): cdef int ctr = -1 cdef list worker_results = list() #Get computational basis - cdef list comp_basis = fusion_ring.get_computational_basis(a,b,n_strands) + cdef list comp_basis = fusion_ring.get_computational_basis(a, b, n_strands) cdef dict basis_dict = {elt: i for i, elt in enumerate(comp_basis)} cdef int dim = len(comp_basis) cdef set coords = set() @@ -140,7 +140,7 @@ cdef sig_2k(fusion_ring, tuple args): nnz_pos = tuple(nnz_pos) #Skip repeated entries when k = 1 - if nnz_pos in comp_basis and (basis_dict[nnz_pos],i) not in coords: + if nnz_pos in comp_basis and (basis_dict[nnz_pos], i) not in coords: m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] #A few special cases top_left = m[0] @@ -152,21 +152,21 @@ cdef sig_2k(fusion_ring, tuple args): #Handle the special case k = 1 if k == 1: - entry = cached_mid_sig_ij(fusion_ring,m[:2],(f,e),a,root) + entry = cached_mid_sig_ij(fusion_ring, m[:2], (f, e), a, root) #Avoid pickling cyclotomic field element objects if must_flatten_coeff: entry = entry.list() - worker_results.append(((basis_dict[nnz_pos],i), entry)) - coords.add((basis_dict[nnz_pos],i)) + worker_results.append(((basis_dict[nnz_pos], i), entry)) + coords.add((basis_dict[nnz_pos], i)) continue entry = 0 for p in fusion_ring.basis(): - f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[k-1],m[k],root,l[k-2],p) - f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,e,root,q,p) - entry += f1 * cached_mid_sig_ij(fusion_ring,(m[k-1],m[k]),(f,e),a,p) * f2 + f1 = _fmat(_fvars, _Nk_ij, one, top_left, m[k-1], m[k], root, l[k-2], p) + f2 = _fmat(_fvars, _Nk_ij, one, top_left, f, e, root, q, p) + entry += f1 * cached_mid_sig_ij(fusion_ring, (m[k-1], m[k]), (f, e), a, p) * f2 #Avoid pickling cyclotomic field element objects if must_flatten_coeff: @@ -224,7 +224,7 @@ cdef odd_one_out(fusion_ring, tuple args): #Handle a couple of small special cases if n_strands == 3: - entry = cached_odd_one_out_ij(fusion_ring,m[-1],f,a,b) + entry = cached_odd_one_out_ij(fusion_ring, m[-1], f, a, b) #Avoid pickling cyclotomic field element objects if must_flatten_coeff: @@ -240,15 +240,15 @@ cdef odd_one_out(fusion_ring, tuple args): #Compute relevant entry entry = 0 for p in basis: - f1 = _fmat(_fvars,_Nk_ij,one,top_left,m[-1],a,root,l[-1],p) - f2 = _fmat(_fvars,_Nk_ij,one,top_left,f,a,root,q,p) - entry += f1 * cached_odd_one_out_ij(fusion_ring,m[-1],f,a,p) * f2 + f1 = _fmat(_fvars, _Nk_ij, one, top_left, m[-1], a, root, l[-1], p) + f2 = _fmat(_fvars, _Nk_ij, one, top_left, f, a, root, q, p) + entry += f1 * cached_odd_one_out_ij(fusion_ring, m[-1], f, a, p) * f2 #Avoid pickling cyclotomic field element objects if must_flatten_coeff: entry = entry.list() - worker_results.append(((basis_dict[nnz_pos],i), entry)) + worker_results.append(((basis_dict[nnz_pos], i), entry)) return worker_results ############################## @@ -257,7 +257,7 @@ cdef odd_one_out(fusion_ring, tuple args): #Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { - "sig_2k": sig_2k, + "sig_2k": sig_2k, "odd_one_out": odd_one_out } @@ -279,17 +279,17 @@ cpdef executor(tuple params): TESTS:: sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import executor - sage: FR = FusionRing("A1",4) - sage: FR.fusion_labels(['idd','one','two','three','four'],inject_variables=True) + sage: FR = FusionRing("A1", 4) + sage: FR.fusion_labels(['idd', 'one', 'two', 'three', 'four'], inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time - sage: params = (('sig_2k',id(FR)),(0,1,(1,one,one,5))) # long time + sage: params = (('sig_2k', id(FR)), (0, 1, (1, one, one, 5))) # long time sage: len(executor(params)) == 13 # long time True sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import executor - sage: FR = FusionRing("A1",2) - sage: FR.fusion_labels("a",inject_variables=True) + sage: FR = FusionRing("A1", 2) + sage: FR.fusion_labels("a", inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: params = (('odd_one_out',id(FR)),(0,1,(a2,a2,5))) + sage: params = (('odd_one_out', id(FR)), (0, 1, (a2, a2, 5))) sage: len(executor(params)) == 1 True """ @@ -297,7 +297,7 @@ cpdef executor(tuple params): #Construct a reference to global FMatrix object in this worker's memory fusion_ring_obj = cast(fr_id, py_object).value #Bind module method to FMatrix object in worker process, and call the method - return mappers[fn_name](fusion_ring_obj,args) + return mappers[fn_name](fusion_ring_obj, args) ###################################### ### Pickling circumvention helpers ### @@ -314,10 +314,10 @@ cpdef _unflatten_entries(fusion_ring, list entries): EXAMPLES:: sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import _unflatten_entries - sage: fr = FusionRing("B2",2) + sage: fr = FusionRing("B2", 2) sage: F = fr.field() sage: coeff = [F.random_element() for i in range(2)] - sage: entries = [((0,0), coeff[0].list()), ((0,1), coeff[1].list())] + sage: entries = [((0, 0), coeff[0].list()), ((0, 1), coeff[1].list())] sage: _unflatten_entries(fr, entries) sage: all(cyc_elt_obj == c for (coord, cyc_elt_obj), c in zip(entries, coeff)) True diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index bd010614493..be067e64ece 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -71,7 +71,7 @@ class FusionRing(WeylCharacterRing): EXAMPLES:: - sage: A22 = FusionRing("A2",2) + sage: A22 = FusionRing("A2", 2) sage: [f1, f2] = A22.fundamental_weights() sage: M = [A22(x) for x in [0*f1, 2*f1, 2*f2, f1+f2, f2, f1]] sage: [M[3] * x for x in M] @@ -92,7 +92,7 @@ class FusionRing(WeylCharacterRing): [B22(0,0), B22(1,0), B22(0,1), B22(2,0), B22(1,1), B22(0,2)] sage: [x.weight() for x in b] [(0, 0), (1, 0), (1/2, 1/2), (2, 0), (3/2, 1/2), (1, 1)] - sage: B22.fusion_labels(['I0','Y1','X','Z','Xp','Y2'], inject_variables=True) + sage: B22.fusion_labels(['I0', 'Y1', 'X', 'Z', 'Xp', 'Y2'], inject_variables=True) sage: b = [B22(x) for x in B22.get_order()]; b [I0, Y1, X, Z, Xp, Y2] sage: [(x, x.weight()) for x in b] @@ -111,7 +111,7 @@ class FusionRing(WeylCharacterRing): This is the order used by methods such as :meth:`s_matrix`. You may use :meth:`CombinatorialFreeModule.set_order` to reorder the basis:: - sage: B22.set_order([x.weight() for x in [I0,Y1,Y2,X,Xp,Z]]) + sage: B22.set_order([x.weight() for x in [I0, Y1, Y2, X, Xp, Z]]) sage: [B22(x) for x in B22.get_order()] [I0, Y1, Y2, X, Xp, Z] @@ -158,7 +158,7 @@ class FusionRing(WeylCharacterRing): .. MATH:: - N^k_{ij} = \sum_l \frac{s(i,\ell)\,s(j,\ell)\,\overline{s(k,\ell)}}{s(I,\ell)}, + N^k_{ij} = \sum_l \frac{s(i, \ell)\, s(j, \ell)\, \overline{s(k, \ell)}}{s(I, \ell)}, where `N^k_{ij}` are the fusion coefficients, i.e. the structure constants of the fusion ring, and ``I`` is the unit object. @@ -167,53 +167,53 @@ class FusionRing(WeylCharacterRing): .. MATH:: - s(i*,j) = s(i,j*) = \overline{s(i,j)}. + s(i*, j) = s(i, j*) = \overline{s(i, j)}. This is equation (16.5) in [DFMS1996]_. Thus with `N_{ijk}=N^{k*}_{ij}` the Verlinde formula is equivalent to .. MATH:: - N_{ijk} = \sum_l \frac{s(i,\ell)\,s(j,\ell)\,s(k,\ell)}{s(I,\ell)}, + N_{ijk} = \sum_l \frac{s(i, \ell)\, s(j, \ell)\, s(k, \ell)}{s(I, \ell)}, In this formula `s` is the normalized unitary S-matrix denoted `s` in [BaKi2001]_. We may define a function that corresponds to the right-hand side, except using `\tilde{s}` instead of `s`:: - sage: def V(i,j,k): + sage: def V(i, j, k): ....: R = i.parent() - ....: return sum(R.s_ij(i,l) * R.s_ij(j,l) * R.s_ij(k,l) / R.s_ij(R.one(),l) + ....: return sum(R.s_ij(i, l) * R.s_ij(j, l) * R.s_ij(k, l) / R.s_ij(R.one(), l) ....: for l in R.basis()) - This does not produce ``self.N_ijk(i,j,k)`` exactly, because of the + This does not produce ``self.N_ijk(i, j, k)`` exactly, because of the missing normalization factor. The following code to check the Verlinde formula takes this into account:: sage: def test_verlinde(R): ....: b0 = R.one() ....: c = R.global_q_dimension() - ....: return all(V(i,j,k) == c * R.N_ijk(i,j,k) for i in R.basis() + ....: return all(V(i, j, k) == c * R.N_ijk(i, j, k) for i in R.basis() ....: for j in R.basis() for k in R.basis()) Every fusion ring should pass this test:: - sage: test_verlinde(FusionRing("A2",1)) + sage: test_verlinde(FusionRing("A2", 1)) True - sage: test_verlinde(FusionRing("B4",2)) # long time (.56s) + sage: test_verlinde(FusionRing("B4", 2)) # long time (.56s) True As an exercise, the reader may verify the examples in Section 5.3 of [RoStWa2009]_. Here we check the example of the Ising modular tensor category, which is related - to the BPZ minimal model `M(4,3)` or to an `E_8` coset + to the BPZ minimal model `M(4, 3)` or to an `E_8` coset model. See [DFMS1996]_ Sections 7.4.2 and 18.4.1. [RoStWa2009]_ Example 5.3.4 tells us how to construct it as the conjugate of the `E_8` level 2 :class:`FusionRing`:: - sage: I = FusionRing("E8",2,conjugate=True) - sage: I.fusion_labels(["i0","p","s"],inject_variables=True) + sage: I = FusionRing("E8", 2, conjugate=True) + sage: I.fusion_labels(["i0", "p", "s"], inject_variables=True) sage: b = I.basis().list(); b [i0, p, s] sage: Matrix([[x*y for x in b] for y in b]) # long time (.93s) @@ -224,7 +224,7 @@ class FusionRing(WeylCharacterRing): [0, 1, 1/8] sage: [x.ribbon() for x in b] [1, -1, zeta128^8] - sage: [I.r_matrix(i, j, k) for (i,j,k) in [(s,s,i0), (p,p,i0), (p,s,s), (s,p,s), (s,s,p)]] + sage: [I.r_matrix(i, j, k) for (i, j, k) in [(s, s, i0), (p, p, i0), (p, s, s), (s, p, s), (s, s, p)]] [-zeta128^56, -1, -zeta128^32, -zeta128^32, zeta128^24] sage: I.r_matrix(s, s, i0) == I.root_of_unity(-1/8) True @@ -245,11 +245,11 @@ class FusionRing(WeylCharacterRing): The term *modular tensor category* refers to the fact that associated with the category there is a projective representation of the modular - group `SL(2,\ZZ)`. We recall that this group is generated by + group `SL(2, \ZZ)`. We recall that this group is generated by .. MATH:: - S = \begin{pmatrix} & -1\\1\end{pmatrix},\qquad + S = \begin{pmatrix} & -1\\1\end{pmatrix}, \qquad T = \begin{pmatrix} 1 & 1\\ &1 \end{pmatrix} subject to the relations `(ST)^3 = S^2`, `S^2T = TS^2`, and `S^4 = I`. @@ -281,7 +281,7 @@ class FusionRing(WeylCharacterRing): of `SL(2, \ZZ)`. Let us confirm these identities for the Fibonacci MTC ``FusionRing("G2", 1)``:: - sage: R = FusionRing("G2",1) + sage: R = FusionRing("G2", 1) sage: S = R.s_matrix(unitary=True) sage: T = R.twists_matrix() sage: C = R.conj_matrix() @@ -347,7 +347,7 @@ def _test_verlinde(self, **options): EXAMPLES:: - sage: G22 = FusionRing("G2",2) + sage: G22 = FusionRing("G2", 2) sage: G22._test_verlinde() """ tester = self._tester(**options) @@ -355,9 +355,9 @@ def _test_verlinde(self, **options): i0 = self.one() from sage.misc.misc import some_tuples B = self.basis() - for x,y,z in some_tuples(B, 3, tester._max_runs): - v = sum(self.s_ij(x,w) * self.s_ij(y,w) * self.s_ij(z,w) / self.s_ij(i0,w) for w in B) - tester.assertEqual(v, c * self.N_ijk(x,y,z)) + for x, y, z in some_tuples(B, 3, tester._max_runs): + v = sum(self.s_ij(x, w) * self.s_ij(y, w) * self.s_ij(z, w) / self.s_ij(i0, w) for w in B) + tester.assertEqual(v, c * self.N_ijk(x, y, z)) def _test_total_q_order(self, **options): r""" @@ -369,7 +369,7 @@ def _test_total_q_order(self, **options): EXAMPLES:: - sage: G22 = FusionRing("G2",2) + sage: G22 = FusionRing("G2", 2) sage: G22._test_total_q_order() """ tester = self._tester(**options) @@ -397,10 +397,10 @@ def test_braid_representation(self, max_strands=6, anyon=None): EXAMPLES:: - sage: A21 = FusionRing("A2",1) + sage: A21 = FusionRing("A2", 1) sage: A21.test_braid_representation(max_strands=4) True - sage: F41 = FusionRing("F4",1) # long time + sage: F41 = FusionRing("F4", 1) # long time sage: F41.test_braid_representation() # long time True """ @@ -409,7 +409,7 @@ def test_braid_representation(self, max_strands=6, anyon=None): b = self.basis() results = [] #Test with different numbers of strands - for n_strands in range(3,max_strands+1): + for n_strands in range(3, max_strands+1): #Randomly select a fusing anyon. Skip the identity element, since #its braiding matrices are trivial if anyon is not None: @@ -426,7 +426,7 @@ def test_braid_representation(self, max_strands=6, anyon=None): if v > 1: d = self(k) break - comp_basis, sig = self.get_braid_generators(a,d,n_strands,verbose=False) + comp_basis, sig = self.get_braid_generators(a, d, n_strands, verbose=False) results.append(len(comp_basis) > 0) results.append(self.gens_satisfy_braid_gp_rels(sig)) return all(results) @@ -509,9 +509,9 @@ def field(self): EXAMPLES:: - sage: FusionRing("A2",2).field() + sage: FusionRing("A2", 2).field() Cyclotomic Field of order 60 and degree 16 - sage: FusionRing("B2",2).field() + sage: FusionRing("B2", 2).field() Cyclotomic Field of order 40 and degree 16 """ # if self._field is None: @@ -550,14 +550,14 @@ def fvars_field(self): EXAMPLES:: - sage: A13 = FusionRing("A1",3,fusion_labels="a",inject_variables=True) + sage: A13 = FusionRing("A1", 3, fusion_labels="a", inject_variables=True) sage: A13.fvars_field() Cyclotomic Field of order 40 and degree 16 sage: A13.field() Cyclotomic Field of order 40 and degree 16 sage: a2**4 2*a0 + 3*a2 - sage: comp_basis, sig = A13.get_braid_generators(a2,a2,3,verbose=False) # long time (<3s) + sage: comp_basis, sig = A13.get_braid_generators(a2, a2, 3, verbose=False) # long time (<3s) sage: A13.fvars_field() # long time Number Field in a with defining polynomial y^32 - ... - 500*y^2 + 25 sage: a2.q_dimension().parent() # long time @@ -572,6 +572,8 @@ def fvars_field(self): """ if self.is_multiplicity_free(): return self.fmats.field() + else: + raise ValueError("Method is only available for multiplicity free fusion rings.") def root_of_unity(self, r, base_coercion=True): r""" @@ -583,7 +585,7 @@ def root_of_unity(self, r, base_coercion=True): EXAMPLES:: - sage: A11 = FusionRing("A1",1) + sage: A11 = FusionRing("A1", 1) sage: A11.field() Cyclotomic Field of order 24 and degree 8 sage: [A11.root_of_unity(2/x) for x in [1..7]] @@ -606,10 +608,10 @@ def get_order(self): EXAMPLES:: - sage: A15 = FusionRing("A1",5) + sage: A15 = FusionRing("A1", 5) sage: w = A15.get_order(); w [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2), (5/2, -5/2)] - sage: A15.set_order([w[k] for k in [0,4,1,3,5,2]]) + sage: A15.set_order([w[k] for k in [0, 4, 1, 3, 5, 2]]) sage: [A15(x) for x in A15.get_order()] [A15(0), A15(4), A15(1), A15(3), A15(5), A15(2)] @@ -645,7 +647,7 @@ def fusion_level(self): EXAMPLES:: - sage: B22 = FusionRing('B2',2) + sage: B22 = FusionRing('B2', 2) sage: B22.fusion_level() 2 """ @@ -664,10 +666,10 @@ def fusion_l(self): EXAMPLES:: - sage: B22 = FusionRing('B2',2) + sage: B22 = FusionRing('B2', 2) sage: B22.fusion_l() 10 - sage: D52 = FusionRing('D5',2) + sage: D52 = FusionRing('D5', 2) sage: D52.fusion_l() 10 """ @@ -721,7 +723,7 @@ def conj_matrix(self): EXAMPLES:: - sage: FusionRing("A2",1).conj_matrix() + sage: FusionRing("A2", 1).conj_matrix() [1 0 0] [0 0 1] [0 1 0] @@ -736,7 +738,7 @@ def twists_matrix(self): EXAMPLES:: - sage: B21=FusionRing("B2",1) + sage: B21=FusionRing("B2", 1) sage: [x.twist() for x in B21.basis().list()] [0, 1, 5/8] sage: [B21.root_of_unity(x.twist()) for x in B21.basis().list()] @@ -769,11 +771,11 @@ def N_ijk(self, elt_i, elt_j, elt_k): sage: G23.fusion_labels("g") sage: b = G23.basis().list(); b [g0, g1, g2, g3, g4, g5] - sage: [(x,y,z) for x in b for y in b for z in b if G23.N_ijk(x,y,z) > 1] + sage: [(x, y, z) for x in b for y in b for z in b if G23.N_ijk(x, y, z) > 1] [(g3, g3, g3), (g3, g3, g4), (g3, g4, g3), (g4, g3, g3)] - sage: all(G23.N_ijk(x,y,z)==G23.N_ijk(y,z,x) for x in b for y in b for z in b) + sage: all(G23.N_ijk(x, y, z)==G23.N_ijk(y, z, x) for x in b for y in b for z in b) True - sage: all(G23.N_ijk(x,y,z)==G23.N_ijk(y,x,z) for x in b for y in b for z in b) + sage: all(G23.N_ijk(x, y, z)==G23.N_ijk(y, x, z) for x in b for y in b for z in b) True """ return (elt_i * elt_j).monomial_coefficients().get(elt_k.dual().weight(), 0) @@ -793,7 +795,7 @@ def Nk_ij(self, elt_i, elt_j, elt_k): sage: A22 = FusionRing("A2", 2) sage: b = A22.basis().list() - sage: all(x*y == sum(A22.Nk_ij(x,y,k)*k for k in b) for x in b for y in b) + sage: all(x*y == sum(A22.Nk_ij(x, y, k)*k for k in b) for x in b for y in b) True """ return (elt_i * elt_j).monomial_coefficients(copy=False).get(elt_k.weight(), 0) @@ -808,7 +810,7 @@ def s_ij(self, elt_i, elt_j, base_coercion=True): .. MATH:: - s_{i,j} = \frac{1}{\theta_i\theta_j} \sum_k N_{ik}^j d_k \theta_k, + s_{i, j} = \frac{1}{\theta_i\theta_j} \sum_k N_{ik}^j d_k \theta_k, where `\theta_k` is the twist and `d_k` is the quantum dimension. See [Row2006]_ Equation (2.2) or [EGNO2015]_ @@ -836,7 +838,7 @@ def s_ij(self, elt_i, elt_j, base_coercion=True): def s_ijconj(self, elt_i, elt_j, base_coercion=True): """ Return the conjugate of the element of the S-matrix given by - ``self.s_ij(elt_i,elt_j,base_coercion=base_coercion)``. + ``self.s_ij(elt_i, elt_j, base_coercion=base_coercion)``. See :meth:`s_ij`. @@ -852,19 +854,19 @@ def s_ijconj(self, elt_i, elt_j, base_coercion=True): TESTS:: - sage: E62 = FusionRing("E6",2) + sage: E62 = FusionRing("E6", 2) sage: E62.fusion_labels("e", inject_variables=True) - sage: E62.s_ij(e8,e1).conjugate() == E62.s_ijconj(e8,e1) + sage: E62.s_ij(e8, e1).conjugate() == E62.s_ijconj(e8, e1) True - sage: F41 = FusionRing("F4",1) + sage: F41 = FusionRing("F4", 1) sage: F41.fmats.find_orthogonal_solution(verbose=False) sage: b = F41.basis() - sage: all(F41.s_ijconj(x,y) == F41._basecoer(F41.s_ij(x,y,base_coercion=False).conjugate()) for x in b for y in b) + sage: all(F41.s_ijconj(x, y) == F41._basecoer(F41.s_ij(x, y, base_coercion=False).conjugate()) for x in b for y in b) True - sage: G22 = FusionRing("G2",2) + sage: G22 = FusionRing("G2", 2) sage: G22.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) sage: b = G22.basis() # long time - sage: all(G22.s_ijconj(x,y) == G22.fmats.field()(G22.s_ij(x,y,base_coercion=False).conjugate()) for x in b for y in b) # long time + sage: all(G22.s_ijconj(x, y) == G22.fmats.field()(G22.s_ij(x, y, base_coercion=False).conjugate()) for x in b for y in b) # long time True """ ret = self.s_ij(elt_i, elt_j, base_coercion=False).conjugate() @@ -944,16 +946,16 @@ def r_matrix(self, i, j, k, base_coercion=True): EXAMPLES:: sage: I = FusionRing("E8", 2, conjugate=True) # Ising MTC - sage: I.fusion_labels(["i0","p","s"], inject_variables=True) - sage: I.r_matrix(s,s,i0) == I.root_of_unity(-1/8) + sage: I.fusion_labels(["i0", "p", "s"], inject_variables=True) + sage: I.r_matrix(s, s, i0) == I.root_of_unity(-1/8) True - sage: I.r_matrix(p,p,i0) + sage: I.r_matrix(p, p, i0) -1 - sage: I.r_matrix(p,s,s) == I.root_of_unity(-1/2) + sage: I.r_matrix(p, s, s) == I.root_of_unity(-1/2) True - sage: I.r_matrix(s,p,s) == I.root_of_unity(-1/2) + sage: I.r_matrix(s, p, s) == I.root_of_unity(-1/2) True - sage: I.r_matrix(s,s,p) == I.root_of_unity(3/8) + sage: I.r_matrix(s, s, p) == I.root_of_unity(3/8) True """ if self.Nk_ij(i, j, k) == 0: @@ -964,8 +966,8 @@ def r_matrix(self, i, j, k, base_coercion=True): i0 = self.one() B = self.basis() ret = sum(y.ribbon(base_coercion=False)**2 / (i.ribbon(base_coercion=False) * x.ribbon(base_coercion=False)**2) - * self.s_ij(i0,y,base_coercion=False) * self.s_ij(i,z,base_coercion=False) * self.s_ijconj(x,z,base_coercion=False) - * self.s_ijconj(k,x,base_coercion=False) * self.s_ijconj(y,z,base_coercion=False) / self.s_ij(i0,z,base_coercion=False) + * self.s_ij(i0, y, base_coercion=False) * self.s_ij(i, z, base_coercion=False) * self.s_ijconj(x, z, base_coercion=False) + * self.s_ijconj(k, x, base_coercion=False) * self.s_ijconj(y, z, base_coercion=False) / self.s_ij(i0, z, base_coercion=False) for x in B for y in B for z in B) / (self.total_q_order(base_coercion=False)**4) if (not base_coercion) or (self._basecoer is None): return ret @@ -980,7 +982,7 @@ def global_q_dimension(self, base_coercion=True): EXAMPLES:: - sage: FusionRing("E6",1).global_q_dimension() + sage: FusionRing("E6", 1).global_q_dimension() 3 """ ret = sum(x.q_dimension(base_coercion=False)**2 for x in self.basis()) @@ -998,7 +1000,7 @@ def total_q_order(self, base_coercion=True): EXAMPLES:: - sage: F = FusionRing("G2",1) + sage: F = FusionRing("G2", 1) sage: tqo=F.total_q_order(); tqo zeta60^15 - zeta60^11 - zeta60^9 + 2*zeta60^3 + zeta60 sage: tqo.is_real_positive() @@ -1007,7 +1009,7 @@ def total_q_order(self, base_coercion=True): True """ c = self.virasoro_central_charge() - ret = self.D_plus(base_coercion=False) * self.root_of_unity(-c/4,base_coercion=False) + ret = self.D_plus(base_coercion=False) * self.root_of_unity(-c/4, base_coercion=False) if (not base_coercion) or (self._basecoer is None): return ret return self._basecoer(ret) @@ -1021,7 +1023,7 @@ def D_plus(self, base_coercion=True): EXAMPLES:: - sage: B31 = FusionRing("B3",1) + sage: B31 = FusionRing("B3", 1) sage: Dp = B31.D_plus(); Dp 2*zeta48^13 - 2*zeta48^5 sage: Dm = B31.D_minus(); Dm @@ -1047,8 +1049,8 @@ def D_minus(self, base_coercion=True): EXAMPLES:: - sage: E83 = FusionRing("E8",3,conjugate=True) - sage: [Dp,Dm] = [E83.D_plus(), E83.D_minus()] + sage: E83 = FusionRing("E8", 3, conjugate=True) + sage: [Dp, Dm] = [E83.D_plus(), E83.D_minus()] sage: Dp*Dm == E83.global_q_dimension() True sage: c = E83.virasoro_central_charge(); c @@ -1071,7 +1073,7 @@ def is_multiplicity_free(self): EXAMPLES:: - sage: [FusionRing(ct,k).is_multiplicity_free() for ct in ("A1","A2","B2","C3") for k in (1,2,3)] + sage: [FusionRing(ct, k).is_multiplicity_free() for ct in ("A1", "A2", "B2", "C3") for k in (1, 2, 3)] [True, True, True, True, True, False, True, True, False, True, False, False] """ ct = self.cartan_type() @@ -1080,8 +1082,8 @@ def is_multiplicity_free(self): if ct.n == 1: return True return k <= 2 - # if ct.letter in ['B','D','G','F','E']: - if ct.letter in ['B','D','F','G']: + # if ct.letter in ['B', 'D', 'G', 'F', 'E']: + if ct.letter in ['B', 'D', 'F', 'G']: return k <= 2 if ct.letter == 'C': if ct.n == 2: @@ -1096,7 +1098,7 @@ def is_multiplicity_free(self): ### Braid group representations ### ################################### - def get_computational_basis(self,a,b,n_strands): + def get_computational_basis(self, a, b, n_strands): r""" Return the so-called computational basis for `\text{Hom}(b, a^n)`. @@ -1107,14 +1109,14 @@ def get_computational_basis(self,a,b,n_strands): - ``n_strands`` -- the number of strands for a braid group Let `n=` ``n_strands`` and let `k` be the greatest integer `\leq n/2`. - The braid group acts on `\text{Hom}(b,a^n)`. This action + The braid group acts on `\text{Hom}(b, a^n)`. This action is computed in :meth:`get_braid_generators`. This method returns the computational basis in the form of a list of fusion trees. Each tree is represented by an `(n-2)`-tuple .. MATH:: - (m_1,\ldots,m_k,l_1,\ldots,l_{k-2}) + (m_1, \ldots, m_k, l_1, \ldots, l_{k-2}) such that each `m_j` is an irreducible constituent in `a \otimes a` and @@ -1122,10 +1124,10 @@ def get_computational_basis(self,a,b,n_strands): .. MATH:: \begin{array}{l} - b \in l_{k-2} \otimes m_{k},\\ - l_{k-2} \in l_{k-3} \otimes m_{k-1},\\ - \cdots,\\ - l_2 \in l_1 \otimes m_3,\\ + b \in l_{k-2} \otimes m_{k}, \\ + l_{k-2} \in l_{k-3} \otimes m_{k-1}, \\ + \cdots, \\ + l_2 \in l_1 \otimes m_3, \\ l_1 \in m_1 \otimes m_2, \end{array} @@ -1139,28 +1141,28 @@ def get_computational_basis(self,a,b,n_strands): EXAMPLES:: - sage: A14 = FusionRing("A1",4) + sage: A14 = FusionRing("A1", 4) sage: A14.get_order() [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2)] - sage: A14.fusion_labels(["zero","one","two","three","four"],inject_variables=True) + sage: A14.fusion_labels(["zero", "one", "two", "three", "four"], inject_variables=True) sage: [A14(x) for x in A14.get_order()] [zero, one, two, three, four] - sage: A14.get_computational_basis(one,two,4) + sage: A14.get_computational_basis(one, two, 4) [(two, two), (two, zero), (zero, two)] """ - def _get_trees(fr,top_row,root): + def _get_trees(fr, top_row, root): if len(top_row) == 2: m1, m2 = top_row - return [[]] if fr.Nk_ij(m1,m2,root) else [] + return [[]] if fr.Nk_ij(m1, m2, root) else [] else: m1, m2 = top_row[:2] - return [tuple([l,*b]) for l in fr.basis() for b in _get_trees(fr,[l]+top_row[2:],root) if fr.Nk_ij(m1,m2,l)] + return [tuple([l, *b]) for l in fr.basis() for b in _get_trees(fr, [l]+top_row[2:], root) if fr.Nk_ij(m1, m2, l)] comp_basis = list() - for top in product((a*a).monomials(),repeat=n_strands//2): + for top in product((a*a).monomials(), repeat=n_strands//2): #If the n_strands is odd, we must extend the top row by a fusing anyon top_row = list(top)+[a]*(n_strands%2) - comp_basis.extend(tuple([*top,*levels]) for levels in _get_trees(self,top_row,b)) + comp_basis.extend(tuple([*top, *levels]) for levels in _get_trees(self, top_row, b)) return comp_basis @lazy_attribute @@ -1173,7 +1175,7 @@ def fmats(self): EXAMPLES:: - sage: A15 = FusionRing("A1",5) + sage: A15 = FusionRing("A1", 5) sage: A15.fmats F-Matrix factory for The Fusion Ring of Type A1 and level 5 with Integer Ring coefficients """ @@ -1206,27 +1208,27 @@ def _emap(self, mapper, input_args, worker_pool=None): EXAMPLES:: - sage: FR = FusionRing("A1",4) - sage: FR.fusion_labels(['idd','one','two','three','four'], inject_variables=True) + sage: FR = FusionRing("A1", 4) + sage: FR.fusion_labels(['idd', 'one', 'two', 'three', 'four'], inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time - sage: len(FR._emap('sig_2k',(1,one,one,5))) # long time + sage: len(FR._emap('sig_2k', (1, one, one, 5))) # long time 13 - sage: FR = FusionRing("A1",2) - sage: FR.fusion_labels("a",inject_variables=True) + sage: FR = FusionRing("A1", 2) + sage: FR.fusion_labels("a", inject_variables=True) sage: FR.fmats.find_orthogonal_solution(verbose=False) - sage: len(FR._emap('odd_one_out',(a1,a1,7))) + sage: len(FR._emap('odd_one_out', (a1, a1, 7))) 16 """ n_proc = worker_pool._processes if worker_pool is not None else 1 input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] no_mp = worker_pool is None #Map phase - input_iter = zip_longest([],input_iter,fillvalue=(mapper,id(self))) + input_iter = zip_longest([], input_iter, fillvalue=(mapper, id(self))) results = list() if no_mp: - mapped = map(executor,input_iter) + mapped = map(executor, input_iter) else: - mapped = worker_pool.imap_unordered(executor,input_iter,chunksize=1) + mapped = worker_pool.imap_unordered(executor, input_iter, chunksize=1) #Reduce phase for worker_results in mapped: results.extend(worker_results) @@ -1263,7 +1265,7 @@ def get_braid_generators(self, we don't run the solver again. - ``use_mp`` -- (default: ``True``) a boolean indicating whether to use multiprocessing to speed up the computation; this is - highly recommended. Python 3.8+ is required. + highly recommended. Python 3.8+ is required. - ``verbose`` -- (default: ``True``) boolean indicating whether to be verbose with the computation @@ -1279,7 +1281,7 @@ def get_braid_generators(self, OUTPUT: - The method outputs a pair of data ``(comp_basis,sig)`` where + The method outputs a pair of data ``(comp_basis, sig)`` where ``comp_basis`` is a list of basis elements of the braid group module, parametrized by a list of fusion ring elements describing a fusion tree. For example with 5 strands the fusion tree @@ -1299,15 +1301,15 @@ def get_braid_generators(self, EXAMPLES:: - sage: A14 = FusionRing("A1",4) + sage: A14 = FusionRing("A1", 4) sage: A14.get_order() [(0, 0), (1/2, -1/2), (1, -1), (3/2, -3/2), (2, -2)] - sage: A14.fusion_labels(["one","two","three","four","five"],inject_variables=True) + sage: A14.fusion_labels(["one", "two", "three", "four", "five"], inject_variables=True) sage: [A14(x) for x in A14.get_order()] [one, two, three, four, five] sage: two ** 5 5*two + 4*four - sage: comp_basis, sig = A14.get_braid_generators(two,two,5,verbose=False) # long time + sage: comp_basis, sig = A14.get_braid_generators(two, two, 5, verbose=False) # long time sage: A14.gens_satisfy_braid_gp_rels(sig) # long time True sage: len(comp_basis) == 5 # long time @@ -1333,17 +1335,17 @@ def get_braid_generators(self, #Set up computational basis and compute generators one at a time a, b = fusing_anyon, total_charge_anyon - comp_basis = self.get_computational_basis(a,b,n_strands) + comp_basis = self.get_computational_basis(a, b, n_strands) d = len(comp_basis) if verbose: print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d, n_strands)) #Compute diagonal odd-indexed generators using the 3j-symbols - gens = {2*i+1: diagonal_matrix(self.r_matrix(a,a,c[i]) for c in comp_basis) for i in range(n_strands//2)} + gens = {2*i+1: diagonal_matrix(self.r_matrix(a, a, c[i]) for c in comp_basis) for i in range(n_strands//2)} #Compute even-indexed generators using F-matrices for k in range(1, n_strands//2): - entries = self._emap('sig_2k',(k,a,b,n_strands),pool) + entries = self._emap('sig_2k', (k, a, b, n_strands), pool) #Build cyclotomic field element objects from tuple of rationals repn _unflatten_entries(self, entries) @@ -1351,7 +1353,7 @@ def get_braid_generators(self, #If n_strands is odd, we compute the final generator if n_strands % 2: - entries = self._emap('odd_one_out',(a,b,n_strands),pool) + entries = self._emap('odd_one_out', (a, b, n_strands), pool) #Build cyclotomic field element objects from tuple of rationals repn _unflatten_entries(self, entries) @@ -1371,16 +1373,16 @@ def gens_satisfy_braid_gp_rels(self, sig): EXAMPLES:: - sage: F41 = FusionRing("F4",1,fusion_labels="f",inject_variables=True) + sage: F41 = FusionRing("F4", 1, fusion_labels="f", inject_variables=True) sage: f1*f1 f0 + f1 - sage: comp, sig = F41.get_braid_generators(f1,f0,4,verbose=False) + sage: comp, sig = F41.get_braid_generators(f1, f0, 4, verbose=False) sage: F41.gens_satisfy_braid_gp_rels(sig) True """ n = len(sig) braid_rels = all(sig[i] * sig[i+1] * sig[i] == sig[i+1] * sig[i] * sig[i+1] for i in range(n-1)) - far_comm = all(sig[i] * sig[j] == sig[j] * sig[i] for i, j in product(range(n),repeat=2) if abs(i-j) > 1 and i > j) + far_comm = all(sig[i] * sig[j] == sig[j] * sig[i] for i, j in product(range(n), repeat=2) if abs(i-j) > 1 and i > j) singular = any(s.is_singular() for s in sig) return braid_rels and far_comm and not singular @@ -1395,7 +1397,7 @@ def is_simple_object(self): EXAMPLES:: sage: A22 = FusionRing("A2", 2) - sage: x = A22(1,0); x + sage: x = A22(1, 0); x A22(1,0) sage: x.is_simple_object() True @@ -1414,7 +1416,7 @@ def weight(self): EXAMPLES:: - sage: A21 = FusionRing("A2",1) + sage: A21 = FusionRing("A2", 1) sage: [x.weight() for x in A21.basis().list()] [(0, 0, 0), (2/3, -1/3, -1/3), (1/3, 1/3, -2/3)] """ @@ -1477,7 +1479,7 @@ def twist(self, reduced=True): else: return twist - def ribbon(self,base_coercion=True): + def ribbon(self, base_coercion=True): r""" Return the twist or ribbon element of ``self``. @@ -1490,15 +1492,15 @@ def ribbon(self,base_coercion=True): EXAMPLES:: - sage: F = FusionRing("A1",3) + sage: F = FusionRing("A1", 3) sage: [x.twist() for x in F.basis()] [0, 3/10, 4/5, 3/2] sage: [x.ribbon(base_coercion=False) for x in F.basis()] [1, zeta40^6, zeta40^12 - zeta40^8 + zeta40^4 - 1, -zeta40^10] - sage: [F.root_of_unity(x,base_coercion=False) for x in [0, 3/10, 4/5, 3/2]] + sage: [F.root_of_unity(x, base_coercion=False) for x in [0, 3/10, 4/5, 3/2]] [1, zeta40^6, zeta40^12 - zeta40^8 + zeta40^4 - 1, -zeta40^10] """ - ret = self.parent().root_of_unity(self.twist(),base_coercion=False) + ret = self.parent().root_of_unity(self.twist(), base_coercion=False) if (not base_coercion) or (self.parent()._basecoer is None): return ret return self.parent()._basecoer(ret) @@ -1508,13 +1510,13 @@ def q_dimension(self, base_coercion=True): r""" Return the quantum dimension as an element of the cyclotomic field of the `2\ell`-th roots of unity, where `l = m (k+h^\vee)` - with `m=1,2,3` depending on whether type is simply, doubly or + with `m=1, 2, 3` depending on whether type is simply, doubly or triply laced, `k` is the level and `h^\vee` is the dual Coxeter number. EXAMPLES:: - sage: B22 = FusionRing("B2",2) + sage: B22 = FusionRing("B2", 2) sage: [(b.q_dimension())^2 for b in B22.basis()] [1, 4, 5, 1, 5, 4] """ diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx index a34fa303d15..ac077f376ae 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -1,5 +1,5 @@ """ -Arithmetic Engine for polynomials as tuples +Arithmetic Engine for Polynomials as Tuples """ # **************************************************************************** # Copyright (C) 2021 Guillermo Aboumrad @@ -20,7 +20,7 @@ cpdef inline tuple poly_to_tup(MPolynomial_libsingular poly): EXAMPLES:: sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: R. = PolynomialRing(QQ) + sage: R. = PolynomialRing(QQ) sage: poly_to_tup(x**2 + 1) (((2, 0), 1), ((0, 0), 1)) sage: poly_to_tup(x**2*y**4 - 4/5*x*y**2 + 1/3 * y) @@ -51,8 +51,8 @@ cpdef inline MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_ sage: from sage.algebras.fusion_rings.poly_tup_engine import _tup_to_poly sage: K = CyclotomicField(20) - sage: R. = PolynomialRing(K) - sage: poly_tup = (((2,0),K.one()), ((0,0),K.one())) + sage: R. = PolynomialRing(K) + sage: poly_tup = (((2, 0), K.one()), ((0, 0), K.one())) sage: _tup_to_poly(poly_tup, parent=R) x^2 + 1 sage: poly = x**2*y**4 - 4/5*x*y**2 + 1/3 * y @@ -63,7 +63,7 @@ cpdef inline MPolynomial_libsingular _tup_to_poly(tuple eq_tup, MPolynomialRing_ TESTS:: sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup, _tup_to_poly - sage: R. = PolynomialRing(CyclotomicField(20)) + sage: R. = PolynomialRing(CyclotomicField(20)) sage: r = R.random_element() sage: _tup_to_poly(poly_to_tup(r), parent=R) == r True @@ -99,7 +99,7 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): EXAMPLES:: sage: from sage.algebras.fusion_rings.poly_tup_engine import _unflatten_coeffs - sage: fm = FMatrix(FusionRing("A2",2)) + sage: fm = FMatrix(FusionRing("A2", 2)) sage: p = fm._poly_ring.random_element() sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: flat_poly_tup = list() @@ -162,14 +162,14 @@ cpdef inline tup_to_univ_poly(tuple eq_tup, univ_poly_ring): sage: from sage.algebras.fusion_rings.poly_tup_engine import tup_to_univ_poly sage: from sage.rings.polynomial.polydict import ETuple sage: K = CyclotomicField(56) - sage: poly_tup = ((ETuple([0,3,0]),K(2)), (ETuple([0,1,0]),K(-1)), (ETuple([0,0,0]),K(-2/3))) + sage: poly_tup = ((ETuple([0, 3, 0]), K(2)), (ETuple([0, 1, 0]), K(-1)), (ETuple([0, 0, 0]), K(-2/3))) sage: R = K['b'] sage: tup_to_univ_poly(poly_tup, R) 2*b^3 - b - 2/3 TESTS:: - sage: poly_tup = ((ETuple([0, 0, 0]), K(-1/5)),) + sage: poly_tup = ((ETuple([0, 0, 0]), K(-1/5)), ) sage: tup_to_univ_poly(poly_tup, R) -1/5 """ @@ -192,9 +192,9 @@ cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): sage: from sage.algebras.fusion_rings.poly_tup_engine import resize sage: from sage.rings.polynomial.polydict import ETuple sage: K = CyclotomicField(56) - sage: poly_tup = ((ETuple([0,3,0,2]),K(2)), (ETuple([0,1,0,1]),K(-1)), (ETuple([0,0,0,0]),K(-2/3))) + sage: poly_tup = ((ETuple([0, 3, 0, 2]), K(2)), (ETuple([0, 1, 0, 1]), K(-1)), (ETuple([0, 0, 0, 0]), K(-2/3))) sage: idx_map = {1: 0, 3: 1} - sage: resize(poly_tup,idx_map,2) + sage: resize(poly_tup, idx_map, 2) (((3, 2), 2), ((1, 1), -1), ((0, 0), -2/3)) sage: R = PolynomialRing(K, 'fx', 20) @@ -202,8 +202,8 @@ cpdef inline tuple resize(tuple eq_tup, dict idx_map, int nvars): Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19 sage: sparse_poly = R(fx0**2 * fx17 + fx3) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup, _tup_to_poly - sage: S. = PolynomialRing(K) - sage: _tup_to_poly(resize(poly_to_tup(sparse_poly),{0:0,3:1,17:2},3), parent=S) + sage: S. = PolynomialRing(K) + sage: _tup_to_poly(resize(poly_to_tup(sparse_poly), {0:0, 3:1, 17:2}, 3), parent=S) x^2*z + y """ cdef ETuple exp, new_e @@ -239,7 +239,7 @@ cpdef list get_variables_degrees(list eqns, int nvars): EXAMPLES:: sage: from sage.algebras.fusion_rings.poly_tup_engine import get_variables_degrees - sage: R. = PolynomialRing(QQ) + sage: R. = PolynomialRing(QQ) sage: polys = [x**2 + 1, x*y*z**2 - 4*x*y, x*z**3 - 4/3*y + 1] sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: get_variables_degrees([poly_to_tup(p) for p in polys], 3) @@ -265,11 +265,11 @@ cpdef list variables(tuple eq_tup): sage: from sage.algebras.fusion_rings.poly_tup_engine import variables sage: from sage.rings.polynomial.polydict import ETuple - sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) + sage: poly_tup = ((ETuple([0, 3, 0]), 2), (ETuple([0, 1, 0]), -1), (ETuple([0, 0, 0]), -2/3)) sage: variables(poly_tup) [1] sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: R. = PolynomialRing(QQ) + sage: R. = PolynomialRing(QQ) sage: variables(poly_to_tup(x*2*y + y**3 - 4/3*x)) [0, 1] sage: variables(poly_to_tup(R(1/4))) @@ -286,12 +286,12 @@ cpdef constant_coeff(tuple eq_tup, field): sage: from sage.algebras.fusion_rings.poly_tup_engine import constant_coeff sage: from sage.rings.polynomial.polydict import ETuple - sage: poly_tup = ((ETuple([0,3,0]),2), (ETuple([0,1,0]),-1), (ETuple([0,0,0]),-2/3)) - sage: constant_coeff(poly_tup,QQ) + sage: poly_tup = ((ETuple([0, 3, 0]), 2), (ETuple([0, 1, 0]), -1), (ETuple([0, 0, 0]), -2/3)) + sage: constant_coeff(poly_tup, QQ) -2/3 - sage: R. = PolynomialRing(QQ) + sage: R. = PolynomialRing(QQ) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: constant_coeff(poly_to_tup(x**5 + x*y*z - 9),QQ) + sage: constant_coeff(poly_to_tup(x**5 + x*y*z - 9), QQ) -9 """ cdef ETuple exp @@ -308,7 +308,7 @@ cpdef tuple apply_coeff_map(tuple eq_tup, coeff_map): sage: from sage.algebras.fusion_rings.poly_tup_engine import apply_coeff_map sage: sq = lambda x : x**2 - sage: R. = PolynomialRing(ZZ) + sage: R. = PolynomialRing(ZZ) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup, _tup_to_poly sage: _tup_to_poly(apply_coeff_map(poly_to_tup(x + 2*y + 3*z), sq), parent=R) x + 4*y + 9*z @@ -454,7 +454,7 @@ cpdef dict compute_known_powers(max_degs, dict val_dict, one): EXAMPLES:: sage: from sage.algebras.fusion_rings.poly_tup_engine import compute_known_powers - sage: R. = PolynomialRing(QQ) + sage: R. = PolynomialRing(QQ) sage: polys = [x**3 + 1, x**2*y + z**3, y**2 - 3*y] sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: known_val = { 0 : poly_to_tup(R(-1)), 2 : poly_to_tup(y**2) } @@ -475,7 +475,7 @@ cpdef dict compute_known_powers(max_degs, dict val_dict, one): max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) cdef dict known_powers #Get polynomial unit as tuple to initialize list elements - cdef tuple one_tup = ((max_deg._new(), one),) + cdef tuple one_tup = ((max_deg._new(), one), ) cdef int d, power, var_idx known_powers = {var_idx: [one_tup]*(d+1) for var_idx, d in max_deg.sparse_iter()} for var_idx, d in max_deg.sparse_iter(): @@ -493,11 +493,11 @@ cdef dict subs(tuple poly_tup, dict known_powers, one): cdef tuple temp for exp, coeff in poly_tup: #Get polynomial unit as tuple - temp = ((exp._new(), one),) + temp = ((exp._new(), one), ) for var_idx, power in exp.sparse_iter(): if var_idx in known_powers: - exp = exp.eadd_p(-power,var_idx) - temp = tup_mul(temp,known_powers[var_idx][power]) + exp = exp.eadd_p(-power, var_idx) + temp = tup_mul(temp, known_powers[var_idx][power]) for m, c in temp: shifted_exp = exp.eadd(m) if shifted_exp in subbed: @@ -552,7 +552,7 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): sage: F = CyclotomicField(20) sage: zeta20 = F.gen() - sage: R. = PolynomialRing(F) + sage: R. = PolynomialRing(F) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_tup_sortkey, poly_to_tup sage: p = (zeta20 + 1)*x^2 + (zeta20^3 + 6)*x*z + (zeta20^2 + 7*zeta20)*z^2 + (2/3*zeta20 + 1/4)*x + y sage: p1 = poly_to_tup(p); p1 @@ -577,4 +577,3 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): key.append(-exp._data[2*i]) key.append(exp._data[2*i+1]) return tuple(key) - diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index fd5fe8d0637..76095a3242d 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -47,7 +47,7 @@ cdef class KSHandler: whether the structure contains an entry corresponding to the given index. The parent process should construct this object without a - ``name`` attribute. Children processes use the ``name`` attribute, + ``name`` attribute. Children processes use the ``name`` attribute, accessed via ``self.shm.name`` to attach to the shared memory block. INPUT: @@ -64,7 +64,7 @@ cdef class KSHandler: .. NOTE:: - To properly dispose of shared memory resources, + To properly dispose of shared memory resources, ``self.shm.unlink()`` must be called before exiting. .. WARNING:: @@ -76,15 +76,15 @@ cdef class KSHandler: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: #Create shared data structure - sage: f = FMatrix(FusionRing("A1",2), inject_variables=True) + sage: f = FMatrix(FusionRing("A1", 2), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() - sage: ks = KSHandler(n,f._field,use_mp=True) + sage: ks = KSHandler(n, f._field, use_mp=True) sage: #In the same shell or in a different shell, attach to fvars sage: name = ks.shm.name - sage: ks2 = KSHandler(n,f._field,name=name,use_mp=True) + sage: ks2 = KSHandler(n, f._field, name=name, use_mp=True) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: eqns = [fx1**2 - 4, fx3**2 + f._field.gen()**4 - 1/19*f._field.gen()**2] sage: ks.update([poly_to_tup(p) for p in eqns]) @@ -104,12 +104,12 @@ cdef class KSHandler: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: #Create shared data structure - sage: f = FMatrix(FusionRing("A1",2), inject_variables=True) + sage: f = FMatrix(FusionRing("A1", 2), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() - sage: ks = KSHandler(n,f._field,use_mp=True) + sage: ks = KSHandler(n, f._field, use_mp=True) sage: TestSuite(ks).run() sage: ks.shm.unlink() sage: f.shutdown_worker_pool() @@ -119,9 +119,9 @@ cdef class KSHandler: n = n_slots d = self.field.degree() ks_t = np.dtype([ - ('known', 'bool', (1,)), - ('nums','i8',(d,)), - ('denoms','u8',(d,)) + ('known', 'bool', (1, )), + ('nums', 'i8', (d, )), + ('denoms', 'u8', (d, )) ]) self.obj_cache = [None]*n if use_mp: @@ -129,23 +129,23 @@ cdef class KSHandler: self.shm = shared_memory.SharedMemory(create=True, size=n*ks_t.itemsize) else: self.shm = shared_memory.SharedMemory(name=name) - self.ks_dat = np.ndarray((n,), dtype=ks_t, buffer=self.shm.buf) + self.ks_dat = np.ndarray((n, ), dtype=ks_t, buffer=self.shm.buf) else: - self.ks_dat = np.ndarray((n,), dtype=ks_t) + self.ks_dat = np.ndarray((n, ), dtype=ks_t) if name is None: - self.ks_dat['known'] = np.zeros((n,1), dtype='bool') - self.ks_dat['nums'] = np.zeros((n,d), dtype='i8') - self.ks_dat['denoms'] = np.ones((n,d), dtype='u8') + self.ks_dat['known'] = np.zeros((n, 1), dtype='bool') + self.ks_dat['nums'] = np.zeros((n, d), dtype='i8') + self.ks_dat['denoms'] = np.ones((n, d), dtype='u8') #Populate initializer data for idx, sq in init_data.items(): - self.setitem(idx,sq) + self.setitem(idx, sq) @cython.nonecheck(False) @cython.wraparound(False) @cython.boundscheck(False) cdef NumberFieldElement_absolute get(self, int idx): r""" - Retrieve the known square corresponding to the given index, + Retrieve the known square corresponding to the given index, if it exists. """ if not self.ks_dat['known'][idx]: @@ -155,9 +155,9 @@ cdef class KSHandler: cdef int d cdef list rat cdef Py_ssize_t i - cdef np.ndarray[np.int64_t,ndim=1] nums = self.ks_dat['nums'][idx] + cdef np.ndarray[np.int64_t, ndim=1] nums = self.ks_dat['nums'][idx] # cdef np.int64_t[::1] num_view = nums - cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.ks_dat['denoms'][idx] + cdef np.ndarray[np.uint64_t, ndim=1] denoms = self.ks_dat['denoms'][idx] # cdef np.uint64_t[::1] denom_view = denoms cdef np.int64_t num cdef np.uint64_t denom @@ -182,25 +182,25 @@ cdef class KSHandler: EXAMPLES:: - sage: f = FMatrix(FusionRing("B5",1)) + sage: f = FMatrix(FusionRing("B5", 1)) sage: f._reset_solver_state() sage: for idx, sq in f._ks.items(): ....: k ....: sage: f.get_orthogonality_constraints() - [fx0^2 - 1, - fx1^2 - 1, - fx2^2 - 1, - fx3^2 - 1, - fx4^2 - 1, - fx5^2 - 1, - fx6^2 - 1, - fx7^2 - 1, - fx8^2 - 1, - fx9^2 - 1, - fx10^2 + fx12^2 - 1, - fx10*fx11 + fx12*fx13, - fx10*fx11 + fx12*fx13, + [fx0^2 - 1, + fx1^2 - 1, + fx2^2 - 1, + fx3^2 - 1, + fx4^2 - 1, + fx5^2 - 1, + fx6^2 - 1, + fx7^2 - 1, + fx8^2 - 1, + fx9^2 - 1, + fx10^2 + fx12^2 - 1, + fx10*fx11 + fx12*fx13, + fx10*fx11 + fx12*fx13, fx11^2 + fx13^2 - 1] sage: f.get_orthogonality_constraints(output=False) sage: f._ks.update(f.ideal_basis) @@ -236,7 +236,7 @@ cdef class KSHandler: try: self.setitem(idx, rhs) except OverflowError: - print("KS overflowed on index {} with value {}".format(idx,self.field(rhs))) + print("KS overflowed on index {} with value {}".format(idx, self.field(rhs))) @cython.nonecheck(False) @cython.wraparound(False) @@ -249,8 +249,8 @@ cdef class KSHandler: list/tuple representation. """ cdef Py_ssize_t i - cdef np.ndarray[np.int64_t,ndim=1] nums = self.ks_dat['nums'][idx] - cdef np.ndarray[np.uint64_t,ndim=1] denoms = self.ks_dat['denoms'][idx] + cdef np.ndarray[np.int64_t, ndim=1] nums = self.ks_dat['nums'][idx] + cdef np.ndarray[np.uint64_t, ndim=1] denoms = self.ks_dat['denoms'][idx] cdef np.int64_t num cdef np.uint64_t denom cdef Rational quo @@ -281,16 +281,16 @@ cdef class KSHandler: TESTS:: - sage: f = FMatrix(FusionRing("C2",2)) + sage: f = FMatrix(FusionRing("C2", 2)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() - sage: ks = KSHandler(n,f._field,use_mp=True,init_data=f._ks) + sage: ks = KSHandler(n, f._field, use_mp=True, init_data=f._ks) sage: #In the same shell or in a different one, attach to shared memory handler sage: name = ks.shm.name - sage: k2 = KSHandler(n,f._field,name=name,use_mp=True) + sage: k2 = KSHandler(n, f._field, name=name, use_mp=True) sage: ks == k2 True sage: ks.shm.unlink() @@ -304,7 +304,7 @@ cdef class KSHandler: TESTS:: - sage: f = FMatrix(FusionRing("A3",1)) + sage: f = FMatrix(FusionRing("A3", 1)) sage: f._reset_solver_state() sage: loads(dumps(f._ks)) == f._ks True @@ -321,12 +321,12 @@ cdef class KSHandler: EXAMPLES:: - sage: f = FMatrix(FusionRing("A3",1)) + sage: f = FMatrix(FusionRing("A3", 1)) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f._ks.update(f.ideal_basis) sage: for idx, sq in f._ks.items(): - ....: print("Index: {}, sq: {}".format(idx,sq)) + ....: print("Index: {}, sq: {}".format(idx, sq)) ....: Index: 0, sq: 1 Index: 1, sq: 1 @@ -342,13 +342,13 @@ cdef class KSHandler: if self.ks_dat['known'][i]: yield i, self.get(i) -def make_KSHandler(n_slots,field,init_data): +def make_KSHandler(n_slots, field, init_data): r""" Provide pickling / unpickling support for :class:`KSHandler`. TESTS:: - sage: f = FMatrix(FusionRing("B4",1)) + sage: f = FMatrix(FusionRing("B4", 1)) sage: f._reset_solver_state() sage: loads(dumps(f._ks)) == f._ks # indirect doctest True @@ -384,7 +384,7 @@ cdef class FvarsHandler: entries have been modified before attempting retrieval. The parent process should construct this object without a - ``name`` attribute. Children processes use the ``name`` attribute, + ``name`` attribute. Children processes use the ``name`` attribute, accessed via ``self.shm.name`` to attach to the shared memory block. Multiprocessing requires Python 3.8+, since we must import the @@ -413,7 +413,7 @@ cdef class FvarsHandler: .. NOTE:: - To properly dispose of shared memory resources, + To properly dispose of shared memory resources, ``self.shm.unlink()`` must be called before exiting. .. NOTE:: @@ -424,7 +424,7 @@ cdef class FvarsHandler: .. WARNING:: - The current data structure supports up to `2^16` entries, + The current data structure supports up to `2^16` entries, with each monomial in each entry having at most 254 nonzero terms. On average, each of the ``max_terms`` monomials can have at most 30 terms. @@ -433,7 +433,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: #Create shared data structure - sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) + sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.start_worker_pool() @@ -442,7 +442,7 @@ cdef class FvarsHandler: sage: fvars = FvarsHandler(8, f._field, f._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name) sage: #In the same shell or in a different shell, attach to fvars sage: name = fvars.shm.name - sage: fvars2 = FvarsHandler(8, f._field, f._idx_to_sextuple, name=name ,use_mp=n_proc, pids_name=pids_name) + sage: fvars2 = FvarsHandler(8, f._field, f._idx_to_sextuple, name=name , use_mp=n_proc, pids_name=pids_name) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: rhs = tuple((exp, tuple(c._coefficients())) for exp, c in poly_to_tup(fx5**5)) sage: fvars[f2, f1, f2, f2, f0, f0] = rhs @@ -451,7 +451,7 @@ cdef class FvarsHandler: sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ - def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, + def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, pids_name=None, name=None, max_terms=20, n_bytes=32): r""" Initialize ``self``. @@ -460,13 +460,13 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: #Create shared data structure - sage: f = FMatrix(FusionRing("A2",1), inject_variables=True) + sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.start_worker_pool() sage: n_proc = f.pool._processes sage: pids_name = f._pid_list.shm.name - sage: fvars = FvarsHandler(8,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) + sage: fvars = FvarsHandler(8, f._field, f._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name) sage: TestSuite(fvars).run(skip="_test_pickling") sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() @@ -478,11 +478,11 @@ cdef class FvarsHandler: cdef int slots = self.bytes // 8 cdef int n_proc = use_mp + 1 self.fvars_t = np.dtype([ - ('modified',np.int8,(n_proc,)), - ('ticks', 'u1', (max_terms,)), - ('exp_data', 'u2', (max_terms*30,)), - ('coeff_nums',np.int64,(max_terms,d,slots)), - ('coeff_denom',np.uint64,(max_terms,d,slots)) + ('modified', np.int8, (n_proc, )), + ('ticks', 'u1', (max_terms, )), + ('exp_data', 'u2', (max_terms*30, )), + ('coeff_nums', np.int64, (max_terms, d, slots)), + ('coeff_denom', np.uint64, (max_terms, d, slots)) ]) self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} self.ngens = n_slots @@ -491,18 +491,18 @@ cdef class FvarsHandler: self.shm = shared_memory.SharedMemory(create=True, size=self.ngens*self.fvars_t.itemsize) else: self.shm = shared_memory.SharedMemory(name=name) - self.fvars = np.ndarray((self.ngens,), dtype=self.fvars_t, buffer=self.shm.buf) + self.fvars = np.ndarray((self.ngens, ), dtype=self.fvars_t, buffer=self.shm.buf) self.pid_list = shared_memory.ShareableList(name=pids_name) self.child_id = -1 else: - self.fvars = np.ndarray((self.ngens,), dtype=self.fvars_t) + self.fvars = np.ndarray((self.ngens, ), dtype=self.fvars_t) self.child_id = 0 #Populate with initialziation data for sextuple, fvar in init_data.items(): if isinstance(fvar, MPolynomial_libsingular): fvar = _flatten_coeffs(poly_to_tup(fvar)) if isinstance(fvar, NumberFieldElement_absolute): - fvar = ((ETuple({},self.ngens), tuple(fvar._coefficients())),) + fvar = ((ETuple({}, self.ngens), tuple(fvar._coefficients())), ) if isinstance(fvar, tuple): transformed = list() for exp, c in fvar: @@ -521,7 +521,7 @@ cdef class FvarsHandler: unflattening its representation and constructing relevant Python objects. - This method returns a tuple of ``(ETuple, coeff)`` pairs, + This method returns a tuple of ``(ETuple, coeff)`` pairs, where ``coeff`` is an element of ``self.field``. EXAMPLES:: @@ -534,7 +534,7 @@ cdef class FvarsHandler: sage: f.start_worker_pool() sage: n_proc = f.pool._processes sage: pids_name = f._pid_list.shm.name - sage: fvars = FvarsHandler(14,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) + sage: fvars = FvarsHandler(14, f._field, f._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name) sage: rhs = tuple((exp, tuple(c._coefficients())) ....: for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx13**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f1, f2, f1, f2, f2, f2)] = rhs @@ -569,7 +569,7 @@ cdef class FvarsHandler: if self.child_id < 0: self.child_id = self.pid_list.index(getpid()) if idx in self.obj_cache: - if self.fvars['modified'][idx,self.child_id]: + if self.fvars['modified'][idx, self.child_id]: del self.obj_cache[idx] else: return self.obj_cache[idx] @@ -582,11 +582,11 @@ cdef class FvarsHandler: cdef Rational quo cdef tuple ret #Define memory views to reduce Python overhead and ensure correct typing - cdef np.ndarray[np.uint8_t,ndim=1] ticks = self.fvars['ticks'][idx] - cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] - cdef np.ndarray[np.int64_t,ndim=3] nums = self.fvars['coeff_nums'][idx] - cdef np.ndarray[np.uint64_t,ndim=3] denoms = self.fvars['coeff_denom'][idx] - cdef np.ndarray[np.int8_t,ndim=1] modified = self.fvars['modified'][idx] + cdef np.ndarray[np.uint8_t, ndim=1] ticks = self.fvars['ticks'][idx] + cdef np.ndarray[np.uint16_t, ndim=1] exp_data = self.fvars['exp_data'][idx] + cdef np.ndarray[np.int64_t, ndim=3] nums = self.fvars['coeff_nums'][idx] + cdef np.ndarray[np.uint64_t, ndim=3] denoms = self.fvars['coeff_denom'][idx] + cdef np.ndarray[np.int8_t, ndim=1] modified = self.fvars['modified'][idx] e = ETuple({}, self.ngens) poly_tup = list() cum = 0 @@ -606,8 +606,8 @@ cdef class FvarsHandler: #Construct cyclotomic field coefficient rats = list() for k in range(self.field.degree()): - num = Integer(list(nums[i,k]),2**63) - denom = Integer(list(denoms[i,k]),2**64) + num = Integer(list(nums[i, k]), 2**63) + denom = Integer(list(denoms[i, k]), 2**64) quo = num / denom rats.append(quo) cyc_coeff = self.field(rats) @@ -622,7 +622,7 @@ cdef class FvarsHandler: @cython.wraparound(False) def __setitem__(self, sextuple, fvar): r""" - Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, + Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, create or overwrite an entry in the shared data structure corresponding to the given sextuple. @@ -636,7 +636,7 @@ cdef class FvarsHandler: sage: f.start_worker_pool() sage: n_proc = f.pool._processes sage: pids_name = f._pid_list.shm.name - sage: fvars = FvarsHandler(27,f._field,f._idx_to_sextuple,use_mp=n_proc,pids_name=pids_name) + sage: fvars = FvarsHandler(27, f._field, f._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name) sage: rhs = tuple((exp, tuple(c._coefficients())) ....: for exp, c in poly_to_tup(1/8*fx0**15 - 23/79*fx2*fx21**3 - 799/2881*fx1*fx2**5*fx10)) sage: fvars[(f3, f2, f1, f2, f1, f3)] = rhs @@ -661,16 +661,16 @@ cdef class FvarsHandler: cdef Rational r idx = self.sext_to_idx[sextuple] #Clear entry before inserting - self.fvars[idx] = np.zeros((1,), dtype=self.fvars_t) + self.fvars[idx] = np.zeros((1, ), dtype=self.fvars_t) #Define memory views to reduce Python overhead and ensure correct typing - cdef np.ndarray[np.uint8_t,ndim=1] ticks = self.fvars['ticks'][idx] - cdef np.ndarray[np.uint16_t,ndim=1] exp_data = self.fvars['exp_data'][idx] - cdef np.ndarray[np.int64_t,ndim=3] nums = self.fvars['coeff_nums'][idx] - cdef np.ndarray[np.uint64_t,ndim=3] denoms = self.fvars['coeff_denom'][idx] - cdef np.ndarray[np.int8_t,ndim=1] modified = self.fvars['modified'][idx] + cdef np.ndarray[np.uint8_t, ndim=1] ticks = self.fvars['ticks'][idx] + cdef np.ndarray[np.uint16_t, ndim=1] exp_data = self.fvars['exp_data'][idx] + cdef np.ndarray[np.int64_t, ndim=3] nums = self.fvars['coeff_nums'][idx] + cdef np.ndarray[np.uint64_t, ndim=3] denoms = self.fvars['coeff_denom'][idx] + cdef np.ndarray[np.int8_t, ndim=1] modified = self.fvars['modified'][idx] cdef list digits #Initialize denominators to 1 - denoms[:,:,0] = 1 + denoms[:, :, 0] = 1 cum = 0 i = 0 for exp, coeff_tup in fvar: @@ -688,19 +688,19 @@ cdef class FvarsHandler: if abs(num) > 2**63 or denom > 2**63: print("Large integers encountered in FvarsHandler", num, denom) if abs(num) < 2**63: - nums[i,k,0] = num + nums[i, k, 0] = num else: digits = num.digits(2**63) - # assert len(digits) <= self.bytes // 8, "Numerator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(num,num.nbits()//8+1) + # assert len(digits) <= self.bytes // 8, "Numerator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(num, num.nbits()//8+1) for t in range(len(digits)): - nums[i,k,t] = digits[t] + nums[i, k, t] = digits[t] if denom < 2**64: - denoms[i,k,0] = denom + denoms[i, k, 0] = denom else: digits = denom.digits(2**64) - # assert len(digits) <= self.bytes // 8, "Denominator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(denom,denom.nbits()//8+1) + # assert len(digits) <= self.bytes // 8, "Denominator {} is too large for shared FvarsHandler. Use at least {} bytes...".format(denom, denom.nbits()//8+1) for t in range(len(digits)): - denoms[i,k,t] = digits[t] + denoms[i, k, t] = digits[t] k += 1 i += 1 modified[:] = 1 @@ -711,13 +711,13 @@ cdef class FvarsHandler: TESTS:: - sage: f = FMatrix(FusionRing("F4",1)) + sage: f = FMatrix(FusionRing("F4", 1)) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() sage: n_proc = f.pool._processes sage: pids_name = f._pid_list.shm.name - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=n_proc,pids_name=pids_name) + sage: fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars, use_mp=n_proc, pids_name=pids_name) sage: for s, fvar in loads(dumps(fvars)).items(): ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: @@ -727,7 +727,7 @@ cdef class FvarsHandler: n = self.fvars.size idx_map = {i: s for s, i in self.sext_to_idx.items()} d = {s: fvar for s, fvar in self.items()} - return make_FvarsHandler, (n,self.field,idx_map,d) + return make_FvarsHandler, (n, self.field, idx_map, d) def items(self): r""" @@ -743,7 +743,7 @@ cdef class FvarsHandler: creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler - sage: shared_fvars = FvarsHandler(5,f._field,f._idx_to_sextuple,init_data=f._fvars) + sage: shared_fvars = FvarsHandler(5, f._field, f._idx_to_sextuple, init_data=f._fvars) sage: for sextuple, fvar in shared_fvars.items(): ....: if sextuple == (f1, f1, f1, f1, f1, f1): ....: f._tup_to_fpoly(fvar) @@ -753,19 +753,19 @@ cdef class FvarsHandler: for sextuple in self.sext_to_idx: yield sextuple, self[sextuple] -def make_FvarsHandler(n,field,idx_map,init_data): +def make_FvarsHandler(n, field, idx_map, init_data): r""" Provide pickling / unpickling support for :class:`FvarsHandler`. TESTS:: - sage: f = FMatrix(FusionRing("G2",1)) + sage: f = FMatrix(FusionRing("G2", 1)) sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() sage: n_proc = f.pool._processes sage: pids_name = f._pid_list.shm.name - sage: fvars = FvarsHandler(n,f._field,f._idx_to_sextuple,init_data=f._fvars,use_mp=n_proc,pids_name=pids_name) + sage: fvars = FvarsHandler(n, f._field, f._idx_to_sextuple, init_data=f._fvars, use_mp=n_proc, pids_name=pids_name) sage: for s, fvar in loads(dumps(fvars)).items(): # indirect doctest ....: assert f._fvars[s] == f._tup_to_fpoly(fvar) ....: From d60568b5dd4f66de5698fbe1e223ba26fed12361 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Tue, 18 Oct 2022 17:33:39 -0700 Subject: [PATCH 238/414] space after comment character --- src/sage/algebras/fusion_rings/all.py | 3 +- src/sage/algebras/fusion_rings/f_matrix.py | 126 +++++++++--------- .../fast_parallel_fmats_methods.pyx | 103 +++++++------- .../fast_parallel_fusion_ring_braid_repn.pyx | 61 +++++---- src/sage/algebras/fusion_rings/fusion_ring.py | 48 +++---- .../algebras/fusion_rings/poly_tup_engine.pyx | 20 +-- .../algebras/fusion_rings/shm_managers.pyx | 106 +++++++-------- 7 files changed, 232 insertions(+), 235 deletions(-) diff --git a/src/sage/algebras/fusion_rings/all.py b/src/sage/algebras/fusion_rings/all.py index 041ced4f28d..e1dfaa1f2b2 100644 --- a/src/sage/algebras/fusion_rings/all.py +++ b/src/sage/algebras/fusion_rings/all.py @@ -3,7 +3,7 @@ """ # **************************************************************************** -# Copyright (C) 2022 Guillermo Aboumrad +# Copyright (C) 2022 Guillermo Aboumrad # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,4 +18,3 @@ lazy_import('sage.algebras.fusion_rings.f_matrix', ['FMatrix']) del lazy_import - diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 9ec23340e66..f7ba23a588e 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -282,7 +282,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._FR.fusion_labels(fusion_label, inject_variables=True) if not self._FR.is_multiplicity_free(): raise NotImplementedError("FMatrix is only available for multiplicity free FusionRings") - #Set up F-symbols entry by entry + # Set up F-symbols entry by entry n_vars = self.findcases() self._poly_ring = PolynomialRing(self._FR.field(), n_vars, var_prefix) if inject_variables: @@ -290,15 +290,15 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab self._poly_ring.inject_variables(get_main_globals()) self._idx_to_sextuple, self._fvars = self.findcases(output=True) - #Base field attributes + # Base field attributes self._field = self._FR.field() r = self._field.defining_polynomial().roots(ring=QQbar, multiplicities=False)[0] self._qqbar_embedding = self._field.hom([r], QQbar) - #Warm starting + # Warm starting self._chkpt_status = -1 - #Multiprocessing attributes + # Multiprocessing attributes self.mp_thresh = 10000 self.pool = None @@ -407,7 +407,7 @@ def _reset_solver_state(self): self._singles = self.get_fvars_by_size(1, indices=True) self._nnz = self._get_known_nonz() - #Clear relevant caches + # Clear relevant caches [x.q_dimension.clear_cache() for x in self._FR.basis()] self._FR.r_matrix.clear_cache() self._FR.s_ij.clear_cache() @@ -443,7 +443,7 @@ def fmat(self, a, b, c, d, x, y, data=True): or self._FR.Nk_ij(b, c, y) == 0 or self._FR.Nk_ij(a, y, d) == 0): return 0 - #Some known zero F-symbols + # Some known zero F-symbols if a == self._FR.one(): if x == b and y == d: return 1 @@ -460,8 +460,8 @@ def fmat(self, a, b, c, d, x, y, data=True): else: return 0 if data: - #Better to use try/except for speed. Somewhat trivial, but worth - #hours when method is called ~10^11 times + # Better to use try/except for speed. Somewhat trivial, but worth + # hours when method is called ~10^11 times try: return self._fvars[a, b, c, d, x, y] except KeyError: @@ -692,7 +692,7 @@ def get_poly_ring(self): """ return self._poly_ring - #TODO: this method is incredibly slow... improve by keeping track of the cyclotomic polynomials, NOT their roots in QQbar + # TODO: this method is incredibly slow... improve by keeping track of the cyclotomic polynomials, NOT their roots in QQbar def get_non_cyclotomic_roots(self): r""" Return a list of roots that define the extension of the associated @@ -809,11 +809,11 @@ def get_coerce_map_from_fr_cyclotomic_field(self): sage: phi.is_identity() True """ - #If base field is different from associated FusionRing's CyclotomicField, - #return coercion map + # If base field is different from associated FusionRing's CyclotomicField, + # return coercion map try: return self._coerce_map_from_cyc_field - #Otherwise, return identity map CyclotomicField <-> CyclotomicField + # Otherwise, return identity map CyclotomicField <-> CyclotomicField except AttributeError: F = self._FR.field() return F.hom([F.gen()], F) @@ -958,7 +958,7 @@ def get_fvars_by_size(self, n, indices=False): if len(X) == n and len(Y) == n: for x in X: for y in Y: - #Discard trivial 1x1 F-matrix + # Discard trivial 1x1 F-matrix trivial = a == one and x == b and y == d trivial |= b == one and x == a and y == c trivial |= c == one and x == d and y == b @@ -1054,7 +1054,7 @@ def load_fvars(self, filename): """ with open(filename, 'rb') as f: self._fvars, self._non_cyc_roots, self._coerce_map_from_cyc_field, self._qqbar_embedding = pickle.load(f) - #Update state attributes + # Update state attributes self._chkpt_status = 7 self._solved = list(True for v in self._fvars) self._field = self._qqbar_embedding.domain() @@ -1206,7 +1206,7 @@ def _restore_state(self, filename): """ with open(filename, 'rb') as f: state = pickle.load(f) - #Loading saved results pickle + # Loading saved results pickle if len(state) == 4: self.load_fvars(filename) self._chkpt_status = 7 @@ -1281,7 +1281,7 @@ class methods. pass if not hasattr(self, '_nnz'): self._reset_solver_state() - #Set up shared memory resource handlers + # Set up shared memory resource handlers n_proc = cpu_count() if processes is None else processes self._pid_list = shared_memory.ShareableList([0]*(n_proc+1)) pids_name = self._pid_list.shm.name @@ -1294,7 +1294,7 @@ class methods. ks_names = self._ks.shm.name self._shared_fvars = FvarsHandler(n, self._field, self._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name, init_data=self._fvars) fvar_names = self._shared_fvars.shm.name - #Initialize worker pool processes + # Initialize worker pool processes args = (id(self), s_name, vd_name, ks_names, fvar_names, n_proc, pids_name) def init(fmats_id, solved_name, vd_name, ks_names, fvar_names, n_proc, pids_name): """ @@ -1377,7 +1377,7 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, """ if mp_thresh is None: mp_thresh = self.mp_thresh - #Compute multiprocessing parameters + # Compute multiprocessing parameters if worker_pool is not None: try: n = len(input_iter) @@ -1386,13 +1386,13 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, if chunksize is None: chunksize = n // (worker_pool._processes**2) + 1 no_mp = worker_pool is None or n < mp_thresh - #Map phase + # Map phase input_iter = zip_longest([], input_iter, fillvalue=(mapper, id(self))) if no_mp: mapped = map(executor, input_iter) else: mapped = worker_pool.imap_unordered(executor, input_iter, chunksize=chunksize) - #Reduce phase + # Reduce phase results = set() for child_eqns in mapped: if child_eqns is not None: @@ -1601,7 +1601,7 @@ def _triangular_elim(self, eqns=None, verbose=True): if not linear_terms_exist: break _backward_subs(self) - #Compute new reduction params and update eqns + # Compute new reduction params and update eqns self._update_reduction_params(eqns=eqns) if self.pool is not None and len(eqns) > self.mp_thresh: n = self.pool._processes @@ -1673,13 +1673,13 @@ def equations_graph(self, eqns=None): G = Graph() if not eqns: return G - #Eqns could be a list of poly objects or poly tuples stored in internal repn + # Eqns could be a list of poly objects or poly tuples stored in internal repn if isinstance(eqns[0], tuple): G.add_vertices([x for eq_tup in eqns for x in variables(eq_tup)]) else: G.add_vertices([x for eq in eqns for x in eq.variables()]) for eq in eqns: - #Eqns could be a list of poly objects or poly tuples stored in internal repn + # Eqns could be a list of poly objects or poly tuples stored in internal repn if isinstance(eq, tuple): s = [v for v in variables(eq)] else: @@ -1773,7 +1773,7 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb small_comps = list() temp_eqns = list() for comp, comp_eqns in self._partition_eqns(eqns=eqns, verbose=verbose).items(): - #Check if component is too large to process + # Check if component is too large to process if len(comp) > largest_comp: temp_eqns.extend(comp_eqns) else: @@ -1810,17 +1810,17 @@ def _get_component_variety(self, var, eqns): sage: f._get_component_variety(c, eqns) # long time [{216: -1, 292: -1, 319: 1}] """ - #Define smaller poly ring in component vars + # Define smaller poly ring in component vars R = PolynomialRing(self._FR.field(), len(var), 'a', order='lex') - #Zip tuples into R and compute Groebner basis + # Zip tuples into R and compute Groebner basis idx_map = {old: new for new, old in enumerate(sorted(var))} nvars = len(var) eqns = [_unflatten_coeffs(self._field, eq_tup) for eq_tup in eqns] polys = [_tup_to_poly(resize(eq_tup, idx_map, nvars), parent=R) for eq_tup in eqns] var_in_R = Ideal(sorted(polys)).variety(ring=AA) - #Change back to fmats poly ring and append to temp_eqns + # Change back to fmats poly ring and append to temp_eqns inv_idx_map = {v: k for k, v in idx_map.items()} return [{inv_idx_map[i]: value for i, (key, value) in enumerate(sorted(soln.items()))} for soln in var_in_R] @@ -1829,8 +1829,8 @@ def _get_component_variety(self, var, eqns): ####################### # TODO: this can probably be improved by constructing a set of defining polynomials - # and checking, one by one, if it's irreducible over the current field. - # If it is, we construct an extension. Perhaps it's best to go one by one here... + # and checking, one by one, if it's irreducible over the current field. + # If it is, we construct an extension. Perhaps it's best to go one by one here... def attempt_number_field_computation(self): r""" Based on the ``CartanType`` of ``self`` and data @@ -1866,7 +1866,7 @@ def attempt_number_field_computation(self): """ ct = self._FR.cartan_type() k = self._FR._k - #Don't try when k is large and odd for SU(2)_k + # Don't try when k is large and odd for SU(2)_k if ct.letter == 'A': if ct.n == 1 and k >= 9 and k % 2: return False @@ -1917,7 +1917,7 @@ def _get_explicit_solution(self, eqns=None, verbose=True): """ if eqns is None: eqns = self.ideal_basis - #Don't add square fixers when warm starting from a late-stage checkpoint + # Don't add square fixers when warm starting from a late-stage checkpoint if self._chkpt_status < 5: n = self._poly_ring.ngens() one = self._field.one() @@ -1934,21 +1934,21 @@ def _get_explicit_solution(self, eqns=None, verbose=True): must_change_base_field = False phi = F.hom([F.gen()], F) for comp, part in eqns_partition.items(): - #If component has only one equation in a single variable, get a root + # If component has only one equation in a single variable, get a root if len(comp) == 1 and len(part) == 1: - #Attempt to find cyclotomic root + # Attempt to find cyclotomic root univ_poly = tup_to_univ_poly(part[0], R) roots = univ_poly.roots(multiplicities=False) if roots: numeric_fvars[comp[0]] = roots[0] else: - #A real solution is preferred + # A real solution is preferred roots = univ_poly.roots(ring=AA, multiplicities=False) if not roots: roots = univ_poly.roots(ring=QQbar, multiplicities=False) non_cyclotomic_roots.append((comp[0], roots[0])) must_change_base_field = True - #Otherwise, compute the component variety and select a point to obtain a numerical solution + # Otherwise, compute the component variety and select a point to obtain a numerical solution else: sols = self._get_component_variety(comp, part) for fx, rhs in sols[0].items(): @@ -1956,8 +1956,8 @@ def _get_explicit_solution(self, eqns=None, verbose=True): must_change_base_field = True if must_change_base_field: - #Attempt to compute smallest number field containing all the F-symbols - #If calculation takes too long, we use QQbar as the base field + # Attempt to compute smallest number field containing all the F-symbols + # If calculation takes too long, we use QQbar as the base field if self.attempt_number_field_computation(): if verbose: print("Computing appropriate NumberField...") @@ -1970,30 +1970,30 @@ def _get_explicit_solution(self, eqns=None, verbose=True): self._qqbar_embedding = lambda x : x self._non_cyc_roots = bf_elts[1:] - #Embed cyclotomic field into newly constructed base field + # Embed cyclotomic field into newly constructed base field cyc_gen_as_bf_elt = bf_elts.pop(0) phi = self._FR.field().hom([cyc_gen_as_bf_elt], self._field) self._coerce_map_from_cyc_field = phi numeric_fvars = {k : phi(v) for k, v in numeric_fvars.items()} for i, elt in enumerate(bf_elts): numeric_fvars[non_cyclotomic_roots[i][0]] = elt - #Update polynomial ring + # Update polynomial ring self._poly_ring = self._poly_ring.change_ring(self._field) - #Ensure all F-symbols are known + # Ensure all F-symbols are known for fx in numeric_fvars: self._solved[fx] = True nvars = self._poly_ring.ngens() assert sum(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in range(nvars) if not self._solved[fx]]) - #Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) + # Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) self._fvars = {sextuple : apply_coeff_map(rhs, phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({}, nvars), rhs), ) _backward_subs(self, flatten=False) self._fvars = {sextuple : constant_coeff(rhs, self._field) for sextuple, rhs in self._fvars.items()} - #Update base field attributes + # Update base field attributes self._FR._field = self.field() self._FR._basecoer = self.get_coerce_map_from_fr_cyclotomic_field() if self._FR._basecoer: @@ -2099,29 +2099,29 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start return self._reset_solver_state() - #Resume computation from checkpoint + # Resume computation from checkpoint if warm_start: self._restore_state(warm_start) - #Loading from a pickle with solved F-symbols + # Loading from a pickle with solved F-symbols if self._chkpt_status > 5: return # loads_shared_memory = False # if use_mp: - # loads_shared_memory = self.start_worker_pool() + # loads_shared_memory = self.start_worker_pool() if use_mp: self.start_worker_pool() if verbose: print("Computing F-symbols for {} with {} variables...".format(self._FR, self._poly_ring.ngens())) if self._chkpt_status < 1: - #Set up hexagon equations and orthogonality constraints + # Set up hexagon equations and orthogonality constraints self.get_orthogonality_constraints(output=False) self.get_defining_equations('hexagons', output=False) - #Report progress + # Report progress if verbose: print("Set up {} hex and orthogonality constraints...".format(len(self.ideal_basis))) - #Unzip _fvars and link to shared_memory structure if using multiprocessing + # Unzip _fvars and link to shared_memory structure if using multiprocessing if use_mp:# and loads_shared_memory: self._fvars = self._shared_fvars else: @@ -2130,35 +2130,35 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self._checkpoint(checkpoint, 1, verbose=verbose) if self._chkpt_status < 2: - #Set up equations graph. Find GB for each component in parallel. Eliminate variables + # Set up equations graph. Find GB for each component in parallel. Eliminate variables self.ideal_basis = self._par_graph_gb(verbose=verbose) self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(verbose=verbose) - #Report progress + # Report progress if verbose: print("Hex elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) self._checkpoint(checkpoint, 2, verbose=verbose) if self._chkpt_status < 3: - #Set up pentagon equations in parallel + # Set up pentagon equations in parallel self.get_defining_equations('pentagons', output=False) - #Report progress + # Report progress if verbose: print("Set up {} reduced pentagons...".format(len(self.ideal_basis))) self._checkpoint(checkpoint, 3, verbose=verbose) if self._chkpt_status < 4: - #Simplify and eliminate variables + # Simplify and eliminate variables self.ideal_basis.sort(key=poly_tup_sortkey) self._triangular_elim(verbose=verbose) - #Report progress + # Report progress if verbose: print("Pent elim step solved for {} / {} variables".format(sum(self._solved), len(self._poly_ring.gens()))) self._checkpoint(checkpoint, 4, verbose=verbose) - #Try adding degrevlex gb -> elim loop until len(ideal_basis) does not change + # Try adding degrevlex gb -> elim loop until len(ideal_basis) does not change - #Set up new equations graph and compute variety for each component + # Set up new equations graph and compute variety for each component if self._chkpt_status < 5: self.ideal_basis = self._par_graph_gb(term_order="lex", verbose=verbose) self.ideal_basis.sort(key=poly_tup_sortkey) @@ -2166,9 +2166,9 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start self._checkpoint(checkpoint, 5, verbose=verbose) self.shutdown_worker_pool() - #Find numeric values for each F-symbol + # Find numeric values for each F-symbol self._get_explicit_solution(verbose=verbose) - #The calculation was successful, so we may delete checkpoints + # The calculation was successful, so we may delete checkpoints self._chkpt_status = 7 self.clear_equations() if checkpoint: @@ -2203,13 +2203,13 @@ def _fix_gauge(self, algorithm=""): adding equation... fx21 - 1 """ while not all(v for v in self._solved): - #Get a variable that has not been fixed - #In ascending index order, for consistent results + # Get a variable that has not been fixed + # In ascending index order, for consistent results for i, var in enumerate(self._poly_ring.gens()): if not self._solved[i]: break - #Fix var = 1, substitute, and solve equations + # Fix var = 1, substitute, and solve equations self.ideal_basis.add(var-1) print("adding equation...", var-1) self.ideal_basis = set(Ideal(list(self.ideal_basis)).groebner_basis(algorithm=algorithm)) @@ -2244,11 +2244,11 @@ def _substitute_degree_one(self, eqns=None): for eq in eqns: if eq.degree() == 1 and sum(eq.degrees()) <= 2 and eq.lm() not in self._solved: self._fvars[self._var_to_sextuple[eq.lm()]] = -sum(c * m for c, m in zip(eq.coefficients()[1:], eq.monomials()[1:])) / eq.lc() - #Add variable to set of known values and remove this equation + # Add variable to set of known values and remove this equation new_knowns.add(eq.lm()) useless.add(eq) - #Update fvars depending on other variables + # Update fvars depending on other variables for idx, fx in enumerate(self._poly_ring.gens()): if fx in new_knowns: self._solved[idx] = fx diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx index 98983f0e171..f68fca95369 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx @@ -10,12 +10,12 @@ Fast F-Matrix methods cimport cython from sage.algebras.fusion_rings.poly_tup_engine cimport ( - compute_known_powers, - get_variables_degrees, variables, - poly_to_tup, _tup_to_poly, - subs, subs_squares, reduce_poly_dict, resize, - _flatten_coeffs, _unflatten_coeffs, - has_appropriate_linear_term, + compute_known_powers, + get_variables_degrees, variables, + poly_to_tup, _tup_to_poly, + subs, subs_squares, reduce_poly_dict, resize, + _flatten_coeffs, _unflatten_coeffs, + has_appropriate_linear_term, resize ) from sage.algebras.fusion_rings.shm_managers cimport KSHandler, FvarsHandler @@ -81,14 +81,14 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): factory._solved[vars[0]] = True linear_terms_exist = True - #TESTS: + # TESTS: # s = factory._idx_to_sextuple[vars[0]] # factory.test_fvars[s] = tuple() # assert factory.test_fvars[s] == fvars[s], "OG value {}, Shared: {}".format(fvars[s], factory.test_fvars[s]) if len(eq_tup) == 2: idx = has_appropriate_linear_term(eq_tup) if idx < 0: continue - #The chosen term is guaranteed to be univariate in the largest variable + # The chosen term is guaranteed to be univariate in the largest variable exp = eq_tup[idx][0] max_var = exp._data[0] if not factory._solved[max_var]: @@ -100,7 +100,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): factory._solved[max_var] = True linear_terms_exist = True - #TESTS: + # TESTS: # s = factory._idx_to_sextuple[max_var] # factory.test_fvars[s] = ((rhs_exp, rhs_coeff), ) # assert _unflatten_coeffs(factory._field, factory.test_fvars[s]) == fvars[s], "OG value {}, Shared: {}".format(factory.test_fvars[s], fvars[s]) @@ -175,7 +175,7 @@ cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): """ if _Nk_ij(a, b, x) == 0 or _Nk_ij(x, c, d) == 0 or _Nk_ij(b, c, y) == 0 or _Nk_ij(a, y, d) == 0: return 0 - #Some known F-symbols + # Some known F-symbols if a == id_anyon: if x == b and y == d: return 1 @@ -201,19 +201,19 @@ cdef _fmat(fvars, _Nk_ij, id_anyon, a, b, c, d, x, y): # cdef dict _Nk_ij = dict() # cpdef _Nk_ij(factory, proc): -# cdef int coeff -# for a, b, c in product(factory._FR.basis(), repeat=3): -# try: -# coeff = (a*b).monomial_coefficients(copy=False)[c.weight()] -# except: -# coeff = 0 -# _Nk_ij[a, b, c] = coeff +# cdef int coeff +# for a, b, c in product(factory._FR.basis(), repeat=3): +# try: +# coeff = (a*b).monomial_coefficients(copy=False)[c.weight()] +# except: +# coeff = 0 +# _Nk_ij[a, b, c] = coeff # cpdef int _Nk_ij(a, b, c): -# try: -# return (a*b).monomial_coefficients(copy=False)[c.weight()] -# except KeyError: -# return 0 +# try: +# return (a*b).monomial_coefficients(copy=False)[c.weight()] +# except KeyError: +# return 0 # # _Nk_ij = cached_function(_Nk_ij, name='_Nk_ij') @@ -227,8 +227,8 @@ cdef req_cy(tuple basis, r_matrix, dict fvars, Nk_ij, id_anyon, tuple sextuple): as a polynomial object. """ a, b, c, d, e, g = sextuple - #To add typing we need to ensure all fmats.fmat are of the same type? - #Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? + # To add typing we need to ensure all fmats.fmat are of the same type? + # Return fmats._poly_ring.zero() and fmats._poly_ring.one() instead of 0 and 1? lhs = r_matrix(a, c, e, base_coercion=False) * _fmat(fvars, Nk_ij, id_anyon, a, c, b, d, e, g) * r_matrix(b, c, g, base_coercion=False) rhs = 0 for f in basis: @@ -242,21 +242,21 @@ cdef get_reduced_hexagons(factory, tuple mp_params): """ Set up and reduce the hexagon equations corresponding to this worker. """ - #Set up multiprocessing parameters + # Set up multiprocessing parameters cdef list worker_results = list() cdef int child_id, n_proc cdef unsigned long i child_id, n_proc, output = mp_params cdef tuple sextuple, red - #Pre-compute common parameters for speed + # Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) cdef dict fvars cdef bint must_zip_up = False if not output: fvars = {s: factory._poly_ring.gen(i) for i, s in factory._idx_to_sextuple.items()} else: - #Handle both cyclotomic and orthogonal solution method + # Handle both cyclotomic and orthogonal solution method for k, v in factory._fvars.items(): must_zip_up = isinstance(v, tuple) break @@ -272,7 +272,7 @@ cdef get_reduced_hexagons(factory, tuple mp_params): cdef ETuple _nnz = factory._nnz _ks = factory._ks - #Computation loop + # Computation loop it = product(basis, repeat=6) for i in range(len(basis)**6): sextuple = next(it) @@ -281,7 +281,7 @@ cdef get_reduced_hexagons(factory, tuple mp_params): if he: red = reduce_poly_dict(he.dict(), _nnz, _ks, one) - #Avoid pickling cyclotomic coefficients + # Avoid pickling cyclotomic coefficients red = _flatten_coeffs(red) worker_results.append(red) @@ -309,7 +309,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): r""" Set up and reduce the pentagon equations corresponding to this worker. """ - #Set up multiprocessing parameters + # Set up multiprocessing parameters cdef list worker_results = list() cdef int child_id, n_proc child_id, n_proc, output = mp_params @@ -317,9 +317,9 @@ cdef get_reduced_pentagons(factory, tuple mp_params): cdef tuple nonuple, red cdef MPolynomial_libsingular pe - #Pre-compute common parameters for speed + # Pre-compute common parameters for speed cdef tuple basis = tuple(factory._FR.basis()) - #Handle both cyclotomic and orthogonal solution method + # Handle both cyclotomic and orthogonal solution method cdef bint must_zip_up for k, v in factory._fvars.items(): must_zip_up = isinstance(v, tuple) @@ -338,7 +338,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): factory._nnz = factory._get_known_nonz() cdef ETuple _nnz = factory._nnz - #Computation loop + # Computation loop it = product(basis, repeat=9) for i in range(len(basis)**9): nonuple = next(it) @@ -347,7 +347,7 @@ cdef get_reduced_pentagons(factory, tuple mp_params): if pe: red = reduce_poly_dict(pe.dict(), _nnz, _ks, one) - #Avoid pickling cyclotomic coefficients + # Avoid pickling cyclotomic coefficients red = _flatten_coeffs(red) worker_results.append(red) @@ -361,11 +361,11 @@ cdef list update_reduce(factory, list eqns): cdef tuple eq_tup, red, unflat cdef dict eq_dict - #Pre-compute common parameters for speed + # Pre-compute common parameters for speed _field = factory._field one = _field.one() cdef KSHandler _ks = factory._ks - #Update reduction params + # Update reduction params factory._nnz = factory._get_known_nonz() factory._kp = compute_known_powers(factory._var_degs, factory._get_known_vals(), factory._field.one()) cdef dict _kp = factory._kp @@ -373,13 +373,13 @@ cdef list update_reduce(factory, list eqns): for i in range(len(eqns)): eq_tup = eqns[i] - #Construct cyclotomic field elts from list repn + # Construct cyclotomic field elts from list repn unflat = _unflatten_coeffs(_field, eq_tup) eq_dict = subs(unflat, _kp, one) red = reduce_poly_dict(eq_dict, _nnz, _ks, one) - #Avoid pickling cyclotomic coefficients + # Avoid pickling cyclotomic coefficients red = _flatten_coeffs(red) res.append(red) @@ -392,7 +392,7 @@ cdef list compute_gb(factory, tuple args): cdef list res = list() cdef list eqns, sorted_vars eqns, term_order = args - #Define smaller poly ring in component vars + # Define smaller poly ring in component vars sorted_vars = [] cdef tuple eq_tup cdef int fx @@ -402,7 +402,7 @@ cdef list compute_gb(factory, tuple args): sorted_vars = sorted(set(sorted_vars)) cdef MPolynomialRing_libsingular R = PolynomialRing(factory._FR.field(), len(sorted_vars), 'a', order=term_order) - #Zip tuples into R and compute Groebner basis + # Zip tuples into R and compute Groebner basis cdef idx_map = { old : new for new, old in enumerate(sorted_vars) } nvars = len(sorted_vars) F = factory.field() @@ -412,14 +412,14 @@ cdef list compute_gb(factory, tuple args): polys.append(_tup_to_poly(resize(eq_tup, idx_map, nvars), parent=R)) gb = Ideal(sorted(polys)).groebner_basis(algorithm="libsingular:slimgb") - #Change back to fmats poly ring and append to temp_eqns + # Change back to fmats poly ring and append to temp_eqns cdef dict inv_idx_map = { v : k for k, v in idx_map.items() } cdef tuple t nvars = factory._poly_ring.ngens() for p in gb: t = resize(poly_to_tup(p), inv_idx_map, nvars) - #Avoid pickling cyclotomic coefficients + # Avoid pickling cyclotomic coefficients t = _flatten_coeffs(t) res.append(t) @@ -437,7 +437,7 @@ cdef inline list collect_eqns(list eqns): This method is only useful when called after :meth:`executor`, whose function argument appends output to the ``worker_results`` list. """ - #Discard the zero polynomial + # Discard the zero polynomial reduced = set(eqns) - set([tuple()]) return list(reduced) @@ -445,12 +445,12 @@ cdef inline list collect_eqns(list eqns): ### Parallel code executor ### ############################## -#Hard-coded module __dict__-style attribute with visible cdef methods +# Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { - "get_reduced_hexagons": get_reduced_hexagons, - "get_reduced_pentagons": get_reduced_pentagons, - "update_reduce": update_reduce, - "compute_gb": compute_gb, + "get_reduced_hexagons": get_reduced_hexagons, + "get_reduced_pentagons": get_reduced_pentagons, + "update_reduce": update_reduce, + "compute_gb": compute_gb, "pent_verify": pent_verify } @@ -492,9 +492,9 @@ cpdef executor(tuple params): True """ (fn_name, fmats_id), args = params - #Construct a reference to global FMatrix object in this worker's memory + # Construct a reference to global FMatrix object in this worker's memory fmats_obj = cast(fmats_id, py_object).value - #Bind module method to FMatrix object in worker process, and call the method + # Bind module method to FMatrix object in worker process, and call the method return mappers[fn_name](fmats_obj, args) #################### @@ -521,7 +521,7 @@ cdef feq_verif(factory, worker_results, fvars, Nk_ij, id_anyon, tuple nonuple, f @cython.cdivision(True) cdef pent_verify(factory, tuple mp_params): r""" - Generate all the pentagon equations assigned to this process, + Generate all the pentagon equations assigned to this process, and reduce them. """ child_id, n_proc, verbose = mp_params @@ -530,7 +530,7 @@ cdef pent_verify(factory, tuple mp_params): cdef unsigned long long i cdef list worker_results = list() - #Pre-compute common parameters for speed + # Pre-compute common parameters for speed Nk_ij = factory._FR.Nk_ij cdef dict fvars = factory._fvars id_anyon = factory._FR.one() @@ -539,4 +539,3 @@ cdef pent_verify(factory, tuple mp_params): feq_verif(factory, worker_results, fvars, Nk_ij, id_anyon, nonuple) if i % 50000000 == 0 and i and verbose: print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000, len(worker_results))) - diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx index 7d60bdb0d6e..1b0ae45e6d0 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx @@ -28,7 +28,7 @@ cdef mid_sig_ij(fusion_ring, row, col, a, b): This method assumes F-matrices are orthogonal. """ - #Pre-compute common parameters for efficiency + # Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() @@ -39,7 +39,7 @@ cdef mid_sig_ij(fusion_ring, row, col, a, b): cdef list basis = list(fusion_ring.basis()) for c in basis: for d in basis: - ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) + # #Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) f1 = _fmat(_fvars, _Nk_ij, one, a, a, yi, b, xi, c) f2 = _fmat(_fvars, _Nk_ij, one, a, a, a, c, d, yi) f3 = _fmat(_fvars, _Nk_ij, one, a, a, a, c, d, yj) @@ -58,21 +58,21 @@ cdef odd_one_out_ij(fusion_ring, xi, xj, a, b): This method assumes F-matrices are orthogonal. """ - #Pre-compute common parameters for efficiency + # Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() entry = 0 for c in fusion_ring.basis(): - ##Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) + # #Warning: We assume F-matrices are orthogonal!!! (using transpose for inverse) f1 = _fmat(_fvars, _Nk_ij, one, a, a, a, b, xi, c) f2 = _fmat(_fvars, _Nk_ij, one, a, a, a, b, xj, c) r = fusion_ring.r_matrix(a, a, c) entry += f1 * r * f2 return entry -#Cache methods (manually for cdef methods) +# Cache methods (manually for cdef methods) cdef odd_one_out_ij_cache = dict() cdef mid_sig_ij_cache = dict() @@ -102,7 +102,7 @@ cdef sig_2k(fusion_ring, tuple args): r""" Compute entries of the `2k`-th braid generator """ - #Pre-compute common parameters for efficiency + # Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() @@ -112,37 +112,37 @@ cdef sig_2k(fusion_ring, tuple args): k, a, b, n_strands = fn_args cdef int ctr = -1 cdef list worker_results = list() - #Get computational basis + # Get computational basis cdef list comp_basis = fusion_ring.get_computational_basis(a, b, n_strands) cdef dict basis_dict = {elt: i for i, elt in enumerate(comp_basis)} cdef int dim = len(comp_basis) cdef set coords = set() cdef int i - #Avoid pickling cyclotomic field element objects + # Avoid pickling cyclotomic field element objects cdef bint must_flatten_coeff = fusion_ring.fvars_field() != QQbar cdef list basis = list(fusion_ring.basis()) for i in range(dim): for f in basis: for e in basis: for q in basis: - #Distribute work amongst processes + # Distribute work amongst processes ctr += 1 if ctr % n_proc != child_id: continue - #Compute appropriate possible nonzero row index + # Compute appropriate possible nonzero row index nnz_pos = list(comp_basis[i]) nnz_pos[k-1] = f nnz_pos[k] = e - #Handle the special case k = 1 + # Handle the special case k = 1 if k > 1: nnz_pos[n_strands//2+k-2] = q nnz_pos = tuple(nnz_pos) - #Skip repeated entries when k = 1 + # Skip repeated entries when k = 1 if nnz_pos in comp_basis and (basis_dict[nnz_pos], i) not in coords: m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] - #A few special cases + # A few special cases top_left = m[0] if k >= 3: top_left = l[k-3] @@ -150,11 +150,11 @@ cdef sig_2k(fusion_ring, tuple args): if k - 1 < len(l): root = l[k-1] - #Handle the special case k = 1 + # Handle the special case k = 1 if k == 1: entry = cached_mid_sig_ij(fusion_ring, m[:2], (f, e), a, root) - #Avoid pickling cyclotomic field element objects + # Avoid pickling cyclotomic field element objects if must_flatten_coeff: entry = entry.list() @@ -168,7 +168,7 @@ cdef sig_2k(fusion_ring, tuple args): f2 = _fmat(_fvars, _Nk_ij, one, top_left, f, e, root, q, p) entry += f1 * cached_mid_sig_ij(fusion_ring, (m[k-1], m[k]), (f, e), a, p) * f2 - #Avoid pickling cyclotomic field element objects + # Avoid pickling cyclotomic field element objects if must_flatten_coeff: entry = entry.list() @@ -182,7 +182,7 @@ cdef odd_one_out(fusion_ring, tuple args): Compute entries of the rightmost braid generator, in case we have an odd number of strands. """ - #Pre-compute common parameters for efficiency + # Pre-compute common parameters for efficiency _fvars = fusion_ring.fmats._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() @@ -194,27 +194,27 @@ cdef odd_one_out(fusion_ring, tuple args): child_id, n_proc, fn_args = args a, b, n_strands = fn_args cdef int ctr = -1 - #Get computational basis + # Get computational basis cdef list comp_basis = fusion_ring.get_computational_basis(a, b, n_strands) cdef dict basis_dict = {elt: i for i, elt in enumerate(comp_basis)} cdef int dim = len(comp_basis) - #Avoid pickling cyclotomic field element objects + # Avoid pickling cyclotomic field element objects cdef bint must_flatten_coeff = fusion_ring.fvars_field() != QQbar cdef list basis = list(fusion_ring.basis()) for i in range(dim): for f in basis: for q in basis: - #Distribute work amongst processes + # Distribute work amongst processes ctr += 1 if ctr % n_proc != child_id: continue - #Compute appropriate possible nonzero row index + # Compute appropriate possible nonzero row index nnz_pos_temp = list(comp_basis[i]) nnz_pos_temp[n_strands//2-1] = f - #Handle small special case + # Handle small special case if n_strands > 3: nnz_pos_temp[-1] = q nnz_pos = tuple(nnz_pos_temp) @@ -222,11 +222,11 @@ cdef odd_one_out(fusion_ring, tuple args): if nnz_pos in comp_basis: m, l = comp_basis[i][:n_strands//2], comp_basis[i][n_strands//2:] - #Handle a couple of small special cases + # Handle a couple of small special cases if n_strands == 3: entry = cached_odd_one_out_ij(fusion_ring, m[-1], f, a, b) - #Avoid pickling cyclotomic field element objects + # Avoid pickling cyclotomic field element objects if must_flatten_coeff: entry = entry.list() @@ -237,14 +237,14 @@ cdef odd_one_out(fusion_ring, tuple args): top_left = l[-2] root = b - #Compute relevant entry + # Compute relevant entry entry = 0 for p in basis: f1 = _fmat(_fvars, _Nk_ij, one, top_left, m[-1], a, root, l[-1], p) f2 = _fmat(_fvars, _Nk_ij, one, top_left, f, a, root, q, p) entry += f1 * cached_odd_one_out_ij(fusion_ring, m[-1], f, a, p) * f2 - #Avoid pickling cyclotomic field element objects + # Avoid pickling cyclotomic field element objects if must_flatten_coeff: entry = entry.list() @@ -255,9 +255,9 @@ cdef odd_one_out(fusion_ring, tuple args): ### Parallel code executor ### ############################## -#Hard-coded module __dict__-style attribute with visible cdef methods +# Hard-coded module __dict__-style attribute with visible cdef methods cdef dict mappers = { - "sig_2k": sig_2k, + "sig_2k": sig_2k, "odd_one_out": odd_one_out } @@ -294,9 +294,9 @@ cpdef executor(tuple params): True """ (fn_name, fr_id), args = params - #Construct a reference to global FMatrix object in this worker's memory + # Construct a reference to global FMatrix object in this worker's memory fusion_ring_obj = cast(fr_id, py_object).value - #Bind module method to FMatrix object in worker process, and call the method + # Bind module method to FMatrix object in worker process, and call the method return mappers[fn_name](fusion_ring_obj, args) ###################################### @@ -327,4 +327,3 @@ cpdef _unflatten_entries(fusion_ring, list entries): if F != QQbar: for i, (coord, entry) in enumerate(entries): entries[i] = (coord, F(entry)) - diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index be067e64ece..029300b5d1f 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -2,13 +2,13 @@ Fusion Rings """ # **************************************************************************** -# Copyright (C) 2019 Daniel Bump -# Guillermo Aboumrad -# Travis Scrimshaw -# Nicolas Thiery +# Copyright (C) 2019 Daniel Bump +# Guillermo Aboumrad +# Travis Scrimshaw +# Nicolas Thiery # -# Distributed under the terms of the GNU General Public License (GPL) -# https://www.gnu.org/licenses/ +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ # **************************************************************************** from itertools import product, zip_longest @@ -408,10 +408,10 @@ def test_braid_representation(self, max_strands=6, anyon=None): raise NotImplementedError("only implemented for multiplicity free fusion rings") b = self.basis() results = [] - #Test with different numbers of strands + # Test with different numbers of strands for n_strands in range(3, max_strands+1): - #Randomly select a fusing anyon. Skip the identity element, since - #its braiding matrices are trivial + # Randomly select a fusing anyon. Skip the identity element, since + # its braiding matrices are trivial if anyon is not None: a = anyon else: @@ -421,7 +421,7 @@ def test_braid_representation(self, max_strands=6, anyon=None): break pow = a ** n_strands d = pow.monomials()[0] - #Try to find 'interesting' braid group reps i.e. skip 1-d reps + # Try to find 'interesting' braid group reps i.e. skip 1-d reps for k, v in pow.monomial_coefficients().items(): if v > 1: d = self(k) @@ -515,7 +515,7 @@ def field(self): Cyclotomic Field of order 40 and degree 16 """ # if self._field is None: - # self._field = CyclotomicField(4 * self._cyclotomic_order) + # self._field = CyclotomicField(4 * self._cyclotomic_order) # return self._field return CyclotomicField(4 * self._cyclotomic_order) @@ -1160,7 +1160,7 @@ def _get_trees(fr, top_row, root): comp_basis = list() for top in product((a*a).monomials(), repeat=n_strands//2): - #If the n_strands is odd, we must extend the top row by a fusing anyon + # If the n_strands is odd, we must extend the top row by a fusing anyon top_row = list(top)+[a]*(n_strands%2) comp_basis.extend(tuple([*top, *levels]) for levels in _get_trees(self, top_row, b)) return comp_basis @@ -1222,14 +1222,14 @@ def _emap(self, mapper, input_args, worker_pool=None): n_proc = worker_pool._processes if worker_pool is not None else 1 input_iter = [(child_id, n_proc, input_args) for child_id in range(n_proc)] no_mp = worker_pool is None - #Map phase + # Map phase input_iter = zip_longest([], input_iter, fillvalue=(mapper, id(self))) results = list() if no_mp: mapped = map(executor, input_iter) else: mapped = worker_pool.imap_unordered(executor, input_iter, chunksize=1) - #Reduce phase + # Reduce phase for worker_results in mapped: results.extend(worker_results) return results @@ -1317,7 +1317,7 @@ def get_braid_generators(self, """ if n_strands < 3: raise ValueError("the number of strands must be an integer at least 3") - #Construct associated FMatrix object and solve for F-symbols + # Construct associated FMatrix object and solve for F-symbols if self.fmats._chkpt_status < 7: self.fmats.find_orthogonal_solution(checkpoint=checkpoint, save_results=save_results, @@ -1325,37 +1325,37 @@ def get_braid_generators(self, use_mp=use_mp, verbose=verbose) - #Set multiprocessing parameters. Context can only be set once, so we try to set it + # Set multiprocessing parameters. Context can only be set once, so we try to set it try: set_start_method('fork') except RuntimeError: pass - #Turn off multiprocessing when field is QQbar due to pickling issues introduced by PARI upgrade in trac ticket #30537 + # Turn off multiprocessing when field is QQbar due to pickling issues introduced by PARI upgrade in trac ticket #30537 pool = Pool() if use_mp and self.fvars_field() != QQbar else None - #Set up computational basis and compute generators one at a time + # Set up computational basis and compute generators one at a time a, b = fusing_anyon, total_charge_anyon comp_basis = self.get_computational_basis(a, b, n_strands) d = len(comp_basis) if verbose: print("Computing an {}-dimensional representation of the Artin braid group on {} strands...".format(d, n_strands)) - #Compute diagonal odd-indexed generators using the 3j-symbols + # Compute diagonal odd-indexed generators using the 3j-symbols gens = {2*i+1: diagonal_matrix(self.r_matrix(a, a, c[i]) for c in comp_basis) for i in range(n_strands//2)} - #Compute even-indexed generators using F-matrices + # Compute even-indexed generators using F-matrices for k in range(1, n_strands//2): entries = self._emap('sig_2k', (k, a, b, n_strands), pool) - #Build cyclotomic field element objects from tuple of rationals repn + # Build cyclotomic field element objects from tuple of rationals repn _unflatten_entries(self, entries) gens[2*k] = matrix(dict(entries)) - #If n_strands is odd, we compute the final generator + # If n_strands is odd, we compute the final generator if n_strands % 2: entries = self._emap('odd_one_out', (a, b, n_strands), pool) - #Build cyclotomic field element objects from tuple of rationals repn + # Build cyclotomic field element objects from tuple of rationals repn _unflatten_entries(self, entries) gens[n_strands-1] = matrix(dict(entries)) @@ -1467,7 +1467,7 @@ def twist(self, reduced=True): P = self.parent() rho = P.space().rho() # We copy self.weight() to skip the test (which was already done - # by self.is_simple_object()). + # by self.is_simple_object()). lam = next(iter(self._monomial_coefficients)) inner = lam.inner_product(lam + 2*rho) twist = P._conj * P._nf * inner / P.fusion_l() diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx index ac077f376ae..e14be4106d6 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -222,7 +222,7 @@ cdef inline ETuple degrees(tuple poly_tup): r""" Return the maximal degree of each variable in the polynomial. """ - #Deal with the empty tuple, representing the zero polynomial + # Deal with the empty tuple, representing the zero polynomial if not poly_tup: return ETuple() cdef ETuple max_degs, exp @@ -329,7 +329,7 @@ cdef inline bint tup_fixes_sq(tuple eq_tup): """ if len(eq_tup) != 2: return False - #To access _attributes, we must cdef ETuple + # To access _attributes, we must cdef ETuple cdef ETuple lm = eq_tup[0][0] if lm._nonzero != 1 or lm._data[1] != 2: return False @@ -367,13 +367,13 @@ cdef dict subs_squares(dict eq_dict, KSHandler known_sq): for idx, power in exp.sparse_iter(): if known_sq.contains(idx): coeff *= pow(known_sq.get(idx), power // 2) - #New power is 1 if power is odd + # New power is 1 if power is odd if power & True: new_e[idx] = 1 else: new_e[idx] = power exp = ETuple(new_e, len(exp)) - #If exponent tuple is already present in dictionary, coefficients are added + # If exponent tuple is already present in dictionary, coefficients are added if exp in subbed: subbed[exp] += coeff else: @@ -389,7 +389,7 @@ cdef dict remove_gcf(dict eq_dict, ETuple nonz): variables known to be nonzero. The entries of ``nonz`` are assumed to be some relatively large number, like 100. """ - #Find common variables, filtered according to known nonzeros + # Find common variables, filtered according to known nonzeros cdef ETuple common_powers, exp cdef NumberFieldElement_absolute c common_powers = nonz @@ -474,7 +474,7 @@ cpdef dict compute_known_powers(max_degs, dict val_dict, one): cdef ETuple max_deg = ETuple(list(max_degs)) max_deg = max_deg.emin(ETuple({idx: 100 for idx in val_dict}, len(max_deg))) cdef dict known_powers - #Get polynomial unit as tuple to initialize list elements + # Get polynomial unit as tuple to initialize list elements cdef tuple one_tup = ((max_deg._new(), one), ) cdef int d, power, var_idx known_powers = {var_idx: [one_tup]*(d+1) for var_idx, d in max_deg.sparse_iter()} @@ -492,7 +492,7 @@ cdef dict subs(tuple poly_tup, dict known_powers, one): cdef int var_idx, power cdef tuple temp for exp, coeff in poly_tup: - #Get polynomial unit as tuple + # Get polynomial unit as tuple temp = ((exp._new(), one), ) for var_idx, power in exp.sparse_iter(): if var_idx in known_powers: @@ -532,7 +532,7 @@ cdef tuple monom_sortkey(ETuple exp): """ cdef int deg = exp.unweighted_degree() # for i in range(exp._nonzero): - # exp._data[2*i+1] = -exp._data[2*i+1] + # exp._data[2*i+1] = -exp._data[2*i+1] cdef ETuple rev = exp.reversed().emul(-1) return (deg, rev) @@ -568,9 +568,9 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): cdef int i, l, nnz cdef list key = [] for exp, c in eq_tup: - #Compare by term degree + # Compare by term degree key.append(exp.unweighted_degree()) - #Next compare by term w.r.t. lex order + # Next compare by term w.r.t. lex order for i in range(exp._nonzero): # key.append(exp._length-1-exp._data[2*(nnz-i-1)]) # key.append(-exp._data[2*(nnz-i-1)+1]) diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 76095a3242d..5a90595f6e6 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -47,7 +47,7 @@ cdef class KSHandler: whether the structure contains an entry corresponding to the given index. The parent process should construct this object without a - ``name`` attribute. Children processes use the ``name`` attribute, + ``name`` attribute. Children processes use the ``name`` attribute, accessed via ``self.shm.name`` to attach to the shared memory block. INPUT: @@ -64,7 +64,7 @@ cdef class KSHandler: .. NOTE:: - To properly dispose of shared memory resources, + To properly dispose of shared memory resources, ``self.shm.unlink()`` must be called before exiting. .. WARNING:: @@ -75,14 +75,14 @@ cdef class KSHandler: EXAMPLES:: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler - sage: #Create shared data structure + sage: # Create shared data structure sage: f = FMatrix(FusionRing("A1", 2), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() sage: ks = KSHandler(n, f._field, use_mp=True) - sage: #In the same shell or in a different shell, attach to fvars + sage: # In the same shell or in a different shell, attach to fvars sage: name = ks.shm.name sage: ks2 = KSHandler(n, f._field, name=name, use_mp=True) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup @@ -103,7 +103,7 @@ cdef class KSHandler: EXAMPLES:: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler - sage: #Create shared data structure + sage: # Create shared data structure sage: f = FMatrix(FusionRing("A1", 2), inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 @@ -119,8 +119,8 @@ cdef class KSHandler: n = n_slots d = self.field.degree() ks_t = np.dtype([ - ('known', 'bool', (1, )), - ('nums', 'i8', (d, )), + ('known', 'bool', (1, )), + ('nums', 'i8', (d, )), ('denoms', 'u8', (d, )) ]) self.obj_cache = [None]*n @@ -136,7 +136,7 @@ cdef class KSHandler: self.ks_dat['known'] = np.zeros((n, 1), dtype='bool') self.ks_dat['nums'] = np.zeros((n, d), dtype='i8') self.ks_dat['denoms'] = np.ones((n, d), dtype='u8') - #Populate initializer data + # Populate initializer data for idx, sq in init_data.items(): self.setitem(idx, sq) @@ -145,7 +145,7 @@ cdef class KSHandler: @cython.boundscheck(False) cdef NumberFieldElement_absolute get(self, int idx): r""" - Retrieve the known square corresponding to the given index, + Retrieve the known square corresponding to the given index, if it exists. """ if not self.ks_dat['known'][idx]: @@ -188,19 +188,19 @@ cdef class KSHandler: ....: k ....: sage: f.get_orthogonality_constraints() - [fx0^2 - 1, - fx1^2 - 1, - fx2^2 - 1, - fx3^2 - 1, - fx4^2 - 1, - fx5^2 - 1, - fx6^2 - 1, - fx7^2 - 1, - fx8^2 - 1, - fx9^2 - 1, - fx10^2 + fx12^2 - 1, - fx10*fx11 + fx12*fx13, - fx10*fx11 + fx12*fx13, + [fx0^2 - 1, + fx1^2 - 1, + fx2^2 - 1, + fx3^2 - 1, + fx4^2 - 1, + fx5^2 - 1, + fx6^2 - 1, + fx7^2 - 1, + fx8^2 - 1, + fx9^2 - 1, + fx10^2 + fx12^2 - 1, + fx10*fx11 + fx12*fx13, + fx10*fx11 + fx12*fx13, fx11^2 + fx13^2 - 1] sage: f.get_orthogonality_constraints(output=False) sage: f._ks.update(f.ideal_basis) @@ -230,7 +230,7 @@ cdef class KSHandler: eq_tup = eqns[i] if tup_fixes_sq(eq_tup): rhs = [-v for v in eq_tup[-1][1]] - #eq_tup is guaranteed univariate, so we extract variable idx from lm + # eq_tup is guaranteed univariate, so we extract variable idx from lm lm = eq_tup[0][0] idx = lm._data[0] try: @@ -288,7 +288,7 @@ cdef class KSHandler: sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() sage: ks = KSHandler(n, f._field, use_mp=True, init_data=f._ks) - sage: #In the same shell or in a different one, attach to shared memory handler + sage: # In the same shell or in a different one, attach to shared memory handler sage: name = ks.shm.name sage: k2 = KSHandler(n, f._field, name=name, use_mp=True) sage: ks == k2 @@ -308,7 +308,7 @@ cdef class KSHandler: sage: f._reset_solver_state() sage: loads(dumps(f._ks)) == f._ks True - sage: f.find_orthogonal_solution(verbose=False) #long time + sage: f.find_orthogonal_solution(verbose=False) # long time sage: loads(dumps(f._ks)) == f._ks True """ @@ -384,11 +384,11 @@ cdef class FvarsHandler: entries have been modified before attempting retrieval. The parent process should construct this object without a - ``name`` attribute. Children processes use the ``name`` attribute, + ``name`` attribute. Children processes use the ``name`` attribute, accessed via ``self.shm.name`` to attach to the shared memory block. Multiprocessing requires Python 3.8+, since we must import the - ``multiprocessing.shared_memory`` module. + ``multiprocessing.shared_memory`` module. INPUT: @@ -413,7 +413,7 @@ cdef class FvarsHandler: .. NOTE:: - To properly dispose of shared memory resources, + To properly dispose of shared memory resources, ``self.shm.unlink()`` must be called before exiting. .. NOTE:: @@ -424,7 +424,7 @@ cdef class FvarsHandler: .. WARNING:: - The current data structure supports up to `2^16` entries, + The current data structure supports up to `2^16` entries, with each monomial in each entry having at most 254 nonzero terms. On average, each of the ``max_terms`` monomials can have at most 30 terms. @@ -432,7 +432,7 @@ cdef class FvarsHandler: EXAMPLES:: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler - sage: #Create shared data structure + sage: # Create shared data structure sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 @@ -440,7 +440,7 @@ cdef class FvarsHandler: sage: n_proc = f.pool._processes sage: pids_name = f._pid_list.shm.name sage: fvars = FvarsHandler(8, f._field, f._idx_to_sextuple, use_mp=n_proc, pids_name=pids_name) - sage: #In the same shell or in a different shell, attach to fvars + sage: # In the same shell or in a different shell, attach to fvars sage: name = fvars.shm.name sage: fvars2 = FvarsHandler(8, f._field, f._idx_to_sextuple, name=name , use_mp=n_proc, pids_name=pids_name) sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup @@ -451,7 +451,7 @@ cdef class FvarsHandler: sage: fvars.shm.unlink() sage: f.shutdown_worker_pool() """ - def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, + def __init__(self, n_slots, field, idx_to_sextuple, init_data={}, use_mp=0, pids_name=None, name=None, max_terms=20, n_bytes=32): r""" Initialize ``self``. @@ -459,7 +459,7 @@ cdef class FvarsHandler: EXAMPLES:: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler - sage: #Create shared data structure + sage: # Create shared data structure sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 @@ -478,10 +478,10 @@ cdef class FvarsHandler: cdef int slots = self.bytes // 8 cdef int n_proc = use_mp + 1 self.fvars_t = np.dtype([ - ('modified', np.int8, (n_proc, )), - ('ticks', 'u1', (max_terms, )), - ('exp_data', 'u2', (max_terms*30, )), - ('coeff_nums', np.int64, (max_terms, d, slots)), + ('modified', np.int8, (n_proc, )), + ('ticks', 'u1', (max_terms, )), + ('exp_data', 'u2', (max_terms*30, )), + ('coeff_nums', np.int64, (max_terms, d, slots)), ('coeff_denom', np.uint64, (max_terms, d, slots)) ]) self.sext_to_idx = {s: i for i, s in idx_to_sextuple.items()} @@ -497,7 +497,7 @@ cdef class FvarsHandler: else: self.fvars = np.ndarray((self.ngens, ), dtype=self.fvars_t) self.child_id = 0 - #Populate with initialziation data + # Populate with initialziation data for sextuple, fvar in init_data.items(): if isinstance(fvar, MPolynomial_libsingular): fvar = _flatten_coeffs(poly_to_tup(fvar)) @@ -521,7 +521,7 @@ cdef class FvarsHandler: unflattening its representation and constructing relevant Python objects. - This method returns a tuple of ``(ETuple, coeff)`` pairs, + This method returns a tuple of ``(ETuple, coeff)`` pairs, where ``coeff`` is an element of ``self.field``. EXAMPLES:: @@ -562,10 +562,10 @@ cdef class FvarsHandler: if not sextuple in self.sext_to_idx: raise KeyError('invalid sextuple {}'.format(sextuple)) cdef Py_ssize_t idx = self.sext_to_idx[sextuple] - #Each process builds its own cache, so each process must know - #whether the entry it wants to retrieve has been modified. - #Each process needs to know where to look, so we use an index - #every process agrees on. The pid_list[0] belongs to the main process. + # Each process builds its own cache, so each process must know + # whether the entry it wants to retrieve has been modified. + # Each process needs to know where to look, so we use an index + # every process agrees on. The pid_list[0] belongs to the main process. if self.child_id < 0: self.child_id = self.pid_list.index(getpid()) if idx in self.obj_cache: @@ -581,7 +581,7 @@ cdef class FvarsHandler: cdef Py_ssize_t cum, i, j, k cdef Rational quo cdef tuple ret - #Define memory views to reduce Python overhead and ensure correct typing + # Define memory views to reduce Python overhead and ensure correct typing cdef np.ndarray[np.uint8_t, ndim=1] ticks = self.fvars['ticks'][idx] cdef np.ndarray[np.uint16_t, ndim=1] exp_data = self.fvars['exp_data'][idx] cdef np.ndarray[np.int64_t, ndim=3] nums = self.fvars['coeff_nums'][idx] @@ -592,9 +592,9 @@ cdef class FvarsHandler: cum = 0 count = np.count_nonzero(ticks) for i in range(count): - #Construct new ETuple for each monomial + # Construct new ETuple for each monomial exp = e._new() - #Handle constant coeff + # Handle constant coeff nnz = ticks[i] if ticks[i] < 255 else 0 exp._nonzero = nnz if nnz: @@ -603,7 +603,7 @@ cdef class FvarsHandler: exp._data[j] = exp_data[cum] cum += 1 - #Construct cyclotomic field coefficient + # Construct cyclotomic field coefficient rats = list() for k in range(self.field.degree()): num = Integer(list(nums[i, k]), 2**63) @@ -613,7 +613,7 @@ cdef class FvarsHandler: cyc_coeff = self.field(rats) poly_tup.append((exp, cyc_coeff)) ret = tuple(poly_tup) - #Cache object and reset modified + # Cache object and reset modified self.obj_cache[idx] = ret modified[self.child_id] = 0 return ret @@ -622,7 +622,7 @@ cdef class FvarsHandler: @cython.wraparound(False) def __setitem__(self, sextuple, fvar): r""" - Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, + Given a sextuple of labels and a tuple of ``(ETuple, cyc_coeff)`` pairs, create or overwrite an entry in the shared data structure corresponding to the given sextuple. @@ -660,21 +660,21 @@ cdef class FvarsHandler: cdef Py_ssize_t cum, i, idx, j, k, t cdef Rational r idx = self.sext_to_idx[sextuple] - #Clear entry before inserting + # Clear entry before inserting self.fvars[idx] = np.zeros((1, ), dtype=self.fvars_t) - #Define memory views to reduce Python overhead and ensure correct typing + # Define memory views to reduce Python overhead and ensure correct typing cdef np.ndarray[np.uint8_t, ndim=1] ticks = self.fvars['ticks'][idx] cdef np.ndarray[np.uint16_t, ndim=1] exp_data = self.fvars['exp_data'][idx] cdef np.ndarray[np.int64_t, ndim=3] nums = self.fvars['coeff_nums'][idx] cdef np.ndarray[np.uint64_t, ndim=3] denoms = self.fvars['coeff_denom'][idx] cdef np.ndarray[np.int8_t, ndim=1] modified = self.fvars['modified'][idx] cdef list digits - #Initialize denominators to 1 + # Initialize denominators to 1 denoms[:, :, 0] = 1 cum = 0 i = 0 for exp, coeff_tup in fvar: - #Handle constant coefficient + # Handle constant coefficient if exp._nonzero > 0: ticks[i] = exp._nonzero else: From d68678a1406970cc13011ab0dac577a28b76e7f7 Mon Sep 17 00:00:00 2001 From: Johann Birnick Date: Wed, 19 Oct 2022 20:00:19 +0100 Subject: [PATCH 239/414] Corrected doctring formatting. --- .../polynomial/multi_polynomial_ring_base.pyx | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index f59c521a0bb..2a9c0ac5004 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -355,30 +355,30 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): This function can be called in two ways: - 1. multivariate_interpolation(bound, points, values) + 1. multivariate_interpolation(bound, points, values) - 2. multivariate_interpolation(bound, function) + 2. multivariate_interpolation(bound, function) INPUT: - * "bound" -- either an integer bounding the total degree or a + * `bound` -- either an integer bounding the total degree or a list/tuple of integers bounding the degree of the variables - * "points" -- a list/tuple containing the evaluation points + * `points` -- list/tuple containing the evaluation points - * "values" -- a list/tuple containing the desired values at "points" + * `values` -- list/tuple containing the desired values at `points` - * "function" -- a evaluable function in n variables, where n is the - number of variables of the polynomial ring + * `function` -- evaluable function in n variables, where n is the number + of variables of the polynomial ring OUTPUT: - 1. A polynomial respecting the bounds and having "values" as values - when evaluated at "points". + 1. A polynomial respecting the bounds and having `values` as values + when evaluated at `points`. - 2. A polynomial respecting the bounds and having the same values as - "function" at exactly so many points so that the polynomial is - unique. + 2. A polynomial respecting the bounds and having the same values as + `function` at exactly so many points so that the polynomial is + unique. EXAMPLES:: @@ -411,17 +411,17 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): ALGORITHM: - Solves a linear system of equations with the linear algebra module. - If the points are not specified, it samples exactly as many points - as needed for a unique solution. + Solves a linear system of equations with the linear algebra module. If + the points are not specified, it samples exactly as many points as + needed for a unique solution. - NOTE: + .. NOTE:: It will only run if the base ring is a field, even though it might work otherwise as well. If your base ring is an integral domain, let it run over the fraction field. - WARNING:: + .. WARNING:: If you don't provide point/value pairs but just a function, it will only use as many points as needed for a unique solution with @@ -437,7 +437,8 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): sage: R.multivariate_interpolation(3,F) 1/2*x^3 + x*y + z^2 - 1/2*x + y + 25 - SEEALSO: + .. SEEALSO:: + :meth:`lagrange_polynomial` """ # get ring and number of variables From fce1d8a11f3aa84b3fe8a05e77e202e31ae16a2a Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 20 Oct 2022 10:38:49 +0200 Subject: [PATCH 240/414] Address Sebastien's comments --- src/sage/rings/morphism.pyx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index cc6b6e9efc8..41f5e7f57ce 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -2941,7 +2941,9 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): cdef _update_slots(self, dict _slots): """ - Helper for copying and pickling. + Update information with the given slots. + + Helper function for copying or pickling. EXAMPLES:: @@ -2951,14 +2953,17 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): sage: phi == psi True """ - self._p = _slots['prime'] + self._p = _slots['p'] self._power = _slots['power'] self._q = self._p ** self._power RingHomomorphism._update_slots(self, _slots) cdef dict _extra_slots(self): """ - Helper for copying and pickling. + Return additional information about this morphism + as a dictionary. + + Helper function for copying or pickling. EXAMPLES:: @@ -2973,7 +2978,7 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): True """ slots = RingHomomorphism._extra_slots(self) - slots['prime'] = self._p + slots['p'] = self._p slots['power'] = self._power return slots From 3c7685643bef6f9dbc40a00fa5a4efa735887285 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 20 Oct 2022 11:01:56 +0200 Subject: [PATCH 241/414] retrieve p as domain.characteristic() --- .../rings/finite_rings/hom_finite_field.pyx | 26 ------------------- src/sage/rings/morphism.pyx | 3 +-- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index 81c7620d42d..86d2401ceec 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -826,31 +826,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ return Morphism.__hash__(self) - cdef dict _extra_slots(self): - r""" - Helper function for copying and pickling - - TESTS:: - - sage: k. = GF(5^3) - sage: Frob = k.frobenius_endomorphism(2) - sage: Frob.__reduce__() # indirect doctest - (, - (, - Automorphism group of Finite Field in t of size 5^3, - {}, - {'_codomain': Finite Field in t of size 5^3, - '_domain': Finite Field in t of size 5^3, - '_is_coercion': False, - '_lift': None, - '_power': 2, - '_repr_type_str': None})) - """ - cdef dict slots - slots = FrobeniusEndomorphism_generic._extra_slots(self) - slots['_power'] = self._power - return slots - cdef _update_slots(self, dict slots): r""" Helper function for copying and pickling @@ -869,7 +844,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): True """ FrobeniusEndomorphism_generic._update_slots(self, slots) - self._power = slots['_power'] domain = self.domain() self._degree = domain.degree() self._degree_fixed = domain.degree().gcd(self._power) diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index 41f5e7f57ce..b4f38dcc8ec 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -2953,7 +2953,7 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): sage: phi == psi True """ - self._p = _slots['p'] + self._p = _slots['_domain'].characteristic() self._power = _slots['power'] self._q = self._p ** self._power RingHomomorphism._update_slots(self, _slots) @@ -2978,7 +2978,6 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): True """ slots = RingHomomorphism._extra_slots(self) - slots['p'] = self._p slots['power'] = self._power return slots From a9a4183e3d1d2f27407948d7636b7adc8cb4c6ff Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 20 Oct 2022 11:17:20 +0200 Subject: [PATCH 242/414] power -> _power --- src/sage/rings/morphism.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/morphism.pyx b/src/sage/rings/morphism.pyx index b4f38dcc8ec..eb54dc4a900 100644 --- a/src/sage/rings/morphism.pyx +++ b/src/sage/rings/morphism.pyx @@ -2954,7 +2954,7 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): True """ self._p = _slots['_domain'].characteristic() - self._power = _slots['power'] + self._power = _slots['_power'] self._q = self._p ** self._power RingHomomorphism._update_slots(self, _slots) @@ -2978,7 +2978,7 @@ cdef class FrobeniusEndomorphism_generic(RingHomomorphism): True """ slots = RingHomomorphism._extra_slots(self) - slots['power'] = self._power + slots['_power'] = self._power return slots def _repr_(self): From 61150df051de5c6841fe44a8cd4fc1af9f0d5b82 Mon Sep 17 00:00:00 2001 From: Johann Birnick Date: Thu, 20 Oct 2022 19:37:29 +0100 Subject: [PATCH 243/414] improved documentation, removed warning for non-unique solution --- .../polynomial/multi_polynomial_ring_base.pyx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index 2a9c0ac5004..6e66b079a1e 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -361,23 +361,23 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): INPUT: - * `bound` -- either an integer bounding the total degree or a + * ``bound`` -- either an integer bounding the total degree or a list/tuple of integers bounding the degree of the variables - * `points` -- list/tuple containing the evaluation points + * ``points`` -- list/tuple containing the evaluation points - * `values` -- list/tuple containing the desired values at `points` + * ``values`` -- list/tuple containing the desired values at ``points`` - * `function` -- evaluable function in n variables, where n is the number - of variables of the polynomial ring + * ``function`` -- evaluable function in `n` variables, where `n` is the + number of variables of the polynomial ring OUTPUT: - 1. A polynomial respecting the bounds and having `values` as values - when evaluated at `points`. + 1. A polynomial respecting the bounds and having ``values`` as values + when evaluated at ``points``. 2. A polynomial respecting the bounds and having the same values as - `function` at exactly so many points so that the polynomial is + ``function`` at exactly so many points so that the polynomial is unique. EXAMPLES:: @@ -421,6 +421,9 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): work otherwise as well. If your base ring is an integral domain, let it run over the fraction field. + Also, if the solution is not unique, it spits out one solution, + without any notice that there are more. + .. WARNING:: If you don't provide point/value pairs but just a function, it @@ -428,7 +431,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): the given bounds. In particular it will *not* notice or check whether the result yields the correct evaluation for other points as well. So if you give wrong bounds, you will get a wrong answer - without a warning. + without any warning. sage: def F(a,b,c): ....: return a^3*b + b + c^2 + 25 @@ -497,10 +500,6 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): raise ValueError('Could not find a solution.') solution = sum(coeff[i] * self.monomial(*exponents_space[i]) for i in range(len(exponents_space))) - # warn the user if the solution is not unique - if M.left_kernel().dimension() > 0: - warnings.warn('The solution is not unique.') - return solution def _coerce_map_from_base_ring(self): From eb5833f39ec177f501092470abddf7a74ffc60a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Tornar=C3=ADa?= Date: Thu, 20 Oct 2022 18:03:19 -0300 Subject: [PATCH 244/414] #34681: fix multiplication of points over Integers(n) --- src/sage/schemes/elliptic_curves/ell_point.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 9c9038e0eb5..cac55f7f546 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -641,6 +641,13 @@ def _add_(self, right): ... ZeroDivisionError: Inverse of 28 does not exist (characteristic = 35 = 7*5) + + Checks that :trac:`34681` is fixed:: + + sage: P+P + (15 : 14 : 1) + sage: 2*P + (15 : 14 : 1) """ # Use Prop 7.1.7 of Cohen "A Course in Computational Algebraic # Number Theory" @@ -3505,7 +3512,7 @@ def _acted_upon_(self, other, side): vQ = 0 else: assert len(pariQ) == 2 - vQ = Sequence(tuple(pariQ) + (1,), E.base_field()) + vQ = Sequence(tuple(pariQ) + (1,), E.base_ring()) Q = EllipticCurvePoint_finite_field(E, vQ, check=False) else: From 9bbacfe4e0232a26b2e50c93e8eed1c01c073ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Tornar=C3=ADa?= Date: Thu, 20 Oct 2022 18:04:45 -0300 Subject: [PATCH 245/414] #34681: do not redo multiplication when pari fails --- src/sage/schemes/elliptic_curves/ell_point.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index cac55f7f546..fbb0614c802 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -85,7 +85,7 @@ sage: LCM([2..60])*P Traceback (most recent call last): ... - ZeroDivisionError: Inverse of 1520944668 does not exist (characteristic = 1715761513 = 26927*63719) + ZeroDivisionError: Inverse of 26927 does not exist (characteristic = 1715761513 = 26927*63719) AUTHORS: @@ -630,7 +630,7 @@ def _add_(self, right): sage: LCM([2..60])*P Traceback (most recent call last): ... - ZeroDivisionError: Inverse of 1520944668 does not exist + ZeroDivisionError: Inverse of 26927 does not exist (characteristic = 1715761513 = 26927*63719) sage: N = 35 @@ -639,7 +639,7 @@ def _add_(self, right): sage: 4*P Traceback (most recent call last): ... - ZeroDivisionError: Inverse of 28 does not exist + ZeroDivisionError: Inverse of 7 does not exist (characteristic = 35 = 7*5) Checks that :trac:`34681` is fixed:: @@ -3504,7 +3504,16 @@ def _acted_upon_(self, other, side): try: pariQ = pari.ellmul(E, self, k) - except PariError: + except PariError as err: + if str(err.errdata().component(1)) == "Fp_inv": + val = err.errdata().component(2) + a = val.lift() + N = val.mod() + N1 = N.gcd(a) + N2 = N//N1 + raise ZeroDivisionError( + f"Inverse of {a} does not exist" + f" (characteristic = {N} = {N1}*{N2})") pariQ = None if pariQ is not None: From e5e8cd54b96bcc01beedee0e7ead7404e0dccb33 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Fri, 21 Oct 2022 12:57:19 -0600 Subject: [PATCH 246/414] trac 34662: create permutation from a generator --- src/sage/combinat/permutation.py | 72 +++++++++++++++----------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index f6d802e2beb..0fa32de70ee 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7478,57 +7478,51 @@ def from_cycles(n, cycles, parent=None): sage: Permutation("(-12,2)(3,4)") Traceback (most recent call last): ... - ValueError: All elements should be strictly positive integers, and I just found a non-positive one. + ValueError: all elements should be strictly positive integers, but I found -12 sage: Permutation("(1,2)(2,4)") Traceback (most recent call last): ... - ValueError: an element appears twice in the input + ValueError: the element 2 appears more than once in the input sage: permutation.from_cycles(4, [[1,18]]) Traceback (most recent call last): ... - ValueError: You claimed that this was a permutation on 1...4 but it contains 18 + ValueError: you claimed that this is a permutation on 1...4, but it contains 18 + + TESTS: + + Verify that :trac:`34662` has been fixed:: + + sage: permutation.from_cycles(6, (c for c in [[1,2,3], [4,5,6]])) + [2, 3, 1, 5, 6, 4] """ if parent is None: parent = Permutations(n) - p = list(range(1, n+1)) - - # Is it really a permutation on 1...n ? - flattened_and_sorted = [] - for c in cycles: - flattened_and_sorted.extend(c) - flattened_and_sorted.sort() - - # Empty input - if not flattened_and_sorted: - return parent(p, check_input=False) - - # Only positive elements - if int(flattened_and_sorted[0]) < 1: - raise ValueError("All elements should be strictly positive " - "integers, and I just found a non-positive one.") - - # Really smaller or equal to n ? - if flattened_and_sorted[-1] > n: - raise ValueError("You claimed that this was a permutation on 1..."+ - str(n)+" but it contains "+str(flattened_and_sorted[-1])) - - # Disjoint cycles ? - previous = flattened_and_sorted[0] - 1 - for i in flattened_and_sorted: - if i == previous: - raise ValueError("an element appears twice in the input") - else: - previous = i + # None represents a value of the permutation that has not yet been specified + p = n * [None] for cycle in cycles: - if not cycle: - continue - first = cycle[0] - for i in range(len(cycle)-1): - p[cycle[i]-1] = cycle[i+1] - p[cycle[-1]-1] = first - + for i in range(len(cycle)): + # two consecutive terms in the cycle represent k and p(k) + k = cycle[i] + pk = cycle[(i + 1) % len(cycle)] + + # check that the values are valid + if (int(k) < 1) or (int(pk) < 1): + raise ValueError("all elements should be strictly positive " + f"integers, but I found {min(k, pk)}") + if (k > n) or (pk > n): + raise ValueError("you claimed that this is a permutation on " + f"1...{n}, but it contains {max(k, pk)}") + if p[k - 1] is not None: + raise ValueError(f"the element {k} appears more than once" + " in the input") + + p[k - 1] = pk + # unspecified values are fixed points of the permutation + for i in range(n): + if p[i] is None: + p[i] = i + 1 return parent(p, check_input=False) def from_lehmer_code(lehmer, parent=None): From 93cf36d06796b1065cdd659989b31b47779cf25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 22 Oct 2022 10:33:18 +0200 Subject: [PATCH 247/414] some suggested details --- src/sage/combinat/triangles_FHM.py | 56 ++++++++++++++++++++++--- src/sage/topology/simplicial_complex.py | 6 +++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/triangles_FHM.py b/src/sage/combinat/triangles_FHM.py index 6e962476433..2a9b08b0791 100644 --- a/src/sage/combinat/triangles_FHM.py +++ b/src/sage/combinat/triangles_FHM.py @@ -143,6 +143,21 @@ def __init__(self, poly, variables=None): self._poly = poly self._n = max(self._poly.degree(v) for v in self._vars) + def _ascii_art_(self): + """ + Return the ascii-art representation (as a matrix). + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: ht = H_triangle(1+2*x*y) + sage: ascii_art(ht) + [0 2] + [1 0] + """ + return self.matrix()._ascii_art_() + def _unicode_art_(self): """ Return the unicode representation (as a matrix). @@ -170,7 +185,24 @@ def _repr_(self) -> str: sage: ht H: 2*x*y + 1 """ - return self._prefix + repr(self._poly) + return self._prefix + ": " + repr(self._poly) + + def _latex_(self): + r""" + Return the LaTeX representation (as a matrix). + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: ht = H_triangle(1+2*x*y) + sage: latex(ht) + \left(\begin{array}{rr} + 0 & 2 \\ + 1 & 0 + \end{array}\right) + """ + return self.matrix()._latex_() def __eq__(self, other) -> bool: """ @@ -266,6 +298,20 @@ def matrix(self): """ return _matrix_display(self._poly, variables=self._vars) + def polynomial(self): + """ + Return the triangle as a bare polynomial. + + EXAMPLES:: + + sage: from sage.combinat.triangles_FHM import H_triangle + sage: x, y = polygens(ZZ, 'x,y') + sage: h = H_triangle(1+2*x*y) + sage: h.polynomial() + 2*x*y + 1 + """ + return self._poly + def truncate(self, d): """ Return the truncated triangle. @@ -304,7 +350,7 @@ class M_triangle(Triangle): sage: P.M_triangle() M: x*y - y + 1 """ - _prefix = 'M: ' + _prefix = 'M' def dual(self): """ @@ -407,7 +453,7 @@ class H_triangle(Triangle): """ Class for the H-triangles. """ - _prefix = 'H: ' + _prefix = 'H' def transpose(self): """ @@ -544,7 +590,7 @@ class F_triangle(Triangle): """ Class for the F-triangles. """ - _prefix = 'F: ' + _prefix = 'F' def h(self): """ @@ -641,7 +687,7 @@ class Gamma_triangle(Triangle): """ Class for the Gamma-triangles. """ - _prefix = 'Γ: ' + _prefix = 'Γ' def h(self): r""" diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index 84026ddb17e..53b1919ac66 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -1604,6 +1604,12 @@ def F_triangle(self, S): sage: cs.F_triangle(cs.facets()[0]) F: x^3 + 9*x^2*y + 3*x*y^2 + y^3 + 6*x^2 + 12*x*y + 3*y^2 + 4*x + 3*y + 1 + + TESTS:: + + sage: S = SimplicialComplex([]) + sage: S.F_triangle(S.facets()[0]) + F: 1 """ x, y = polygens(ZZ, 'x, y') from sage.combinat.triangles_FHM import F_triangle From 415670ca7dd9c2789805dbd9d1c8c2cd24c68721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 23 Oct 2022 20:27:46 +0200 Subject: [PATCH 248/414] make Compositions() an additive monoid. --- src/sage/combinat/composition.py | 47 ++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index ee711e52f9f..ab51d83ecfd 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -32,8 +32,8 @@ from itertools import accumulate from collections.abc import Sequence -from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets -from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.categories.enumerated_sets import EnumeratedSets +from sage.categories.additive_monoids import AdditiveMonoids from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.sets.finite_enumerated_set import FiniteEnumeratedSet @@ -1386,6 +1386,7 @@ def count(self, n): """ return sum(i == n for i in self) + Sequence.register(Composition) ############################################################## @@ -1547,14 +1548,14 @@ class Compositions(UniqueRepresentation, Parent): results. It is up to the user to ensure that the inner and outer compositions themselves satisfy the parts and slope constraints. - Note that if you specify ``min_part=0``, then the objects produced may - have parts equal to zero. This violates the internal assumptions - that the composition class makes. Use at your own risk, or - preferably consider using ``IntegerVectors`` instead:: + Note that setting ``min_part=0`` is not allowed:: - sage: Compositions(2, length=3, min_part=0).list() - doctest:...: RuntimeWarning: Currently, setting min_part=0 produces Composition objects which violate internal assumptions. Calling methods on these objects may produce errors or WRONG results! - [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] + sage: Compositions(2, length=3, min_part=0) + Traceback (most recent call last): + ... + ValueError: setting min_part=0 is not allowed for Compositions + + Preferably consider using ``IntegerVectors`` instead:: sage: list(IntegerVectors(2, 3)) [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] @@ -1651,8 +1652,7 @@ def __classcall_private__(self, n=None, **kwargs): if 'min_part' not in kwargs: kwargs['min_part'] = 1 elif kwargs['min_part'] == 0: - from warnings import warn - warn("Currently, setting min_part=0 produces Composition objects which violate internal assumptions. Calling methods on these objects may produce errors or WRONG results!", RuntimeWarning) + raise ValueError("setting min_part=0 is not allowed for Compositions") if 'outer' in kwargs: kwargs['ceiling'] = list(kwargs['outer']) @@ -1673,7 +1673,7 @@ def __classcall_private__(self, n=None, **kwargs): kwargs['min_length'] = len(inner) return IntegerListsLex(n, **kwargs) - def __init__(self, is_infinite=False): + def __init__(self, is_infinite=False, category=None): """ Initialize ``self``. @@ -1682,10 +1682,12 @@ def __init__(self, is_infinite=False): sage: C = Compositions() sage: TestSuite(C).run() """ + if category is None: + category = EnumeratedSets() if is_infinite: - Parent.__init__(self, category=InfiniteEnumeratedSets()) + Parent.__init__(self, category=category.Infinite()) else: - Parent.__init__(self, category=FiniteEnumeratedSets()) + Parent.__init__(self, category=category.Finite()) Element = Composition @@ -1878,7 +1880,8 @@ def __init__(self): sage: C = Compositions() sage: TestSuite(C).run() """ - Compositions.__init__(self, True) + cat = AdditiveMonoids() + Compositions.__init__(self, True, category=cat) def _repr_(self) -> str: """ @@ -1907,6 +1910,20 @@ def subset(self, size=None): return self return Compositions(size) + def zero(self): + """ + Return the zero of the additive monoid. + + This is the empty composition. + + EXAMPLES:: + + sage: C = Compositions() + sage: C.zero() + [] + """ + return Composition([]) + def __iter__(self): """ Iterate over all compositions. From 57a2d1dbe644b7ed4e34f3e3944d2e0297908825 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Tue, 18 Oct 2022 15:34:10 +0100 Subject: [PATCH 249/414] Add test failing when matrix can be generated by paleyI --- src/sage/combinat/matrices/hadamard_matrix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 9ca538ac482..e6826780305 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -411,6 +411,8 @@ def hadamard_matrix(n,existence=False, check=True): Traceback (most recent call last): ... ValueError: The Hadamard matrix of order 10 does not exist + sage: matrix.hadamard(312, existence=True) + True """ if not(n % 4 == 0) and (n > 2): if existence: From 822b99a16841fda42d0123b424dae1076f7b2b47 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Wed, 19 Oct 2022 15:56:46 +0100 Subject: [PATCH 250/414] Try recursive method in hadamard_matrix only if it will be successful --- src/sage/combinat/matrices/hadamard_matrix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index e6826780305..058d39879be 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -430,9 +430,9 @@ def hadamard_matrix(n,existence=False, check=True): if existence: return True M = hadamard_matrix_paleyII(n) - elif n == 4 or n % 8 == 0: + elif n == 4 or n % 8 == 0 and hadamard_matrix(n//2,existence=True) is True: if existence: - return hadamard_matrix(n//2,existence=True) + return True had = hadamard_matrix(n//2,check=False) chad1 = matrix([list(r) + list(r) for r in had.rows()]) mhad = (-1) * had From c1a371e91e505c1abccdf1bdf7d3f12678ad5efa Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Wed, 19 Oct 2022 18:00:33 +0100 Subject: [PATCH 251/414] Add tests for matrices created by skew_hadamard_matrix and regular_symmetric_hadamard_matrix_with_constant_diagonal --- src/sage/combinat/matrices/hadamard_matrix.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 058d39879be..9655c326ccc 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -413,6 +413,10 @@ def hadamard_matrix(n,existence=False, check=True): ValueError: The Hadamard matrix of order 10 does not exist sage: matrix.hadamard(312, existence=True) True + sage: matrix.hadamard(1904, existence=True) + True + sage: matrix.hadamard(324, existence=True) + True """ if not(n % 4 == 0) and (n > 2): if existence: From a411049f1df98af9d26281e2f62e72ac375e49ed Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Wed, 19 Oct 2022 18:17:52 +0100 Subject: [PATCH 252/414] Add skew_hadamard_matrix and regular_symmetric_hadamard_matrix_with_constant_diagonal to hadamard_matrix function --- src/sage/combinat/matrices/hadamard_matrix.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 9655c326ccc..e56722c50b3 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -448,6 +448,14 @@ def hadamard_matrix(n,existence=False, check=True): if existence: return True M = hadamard_matrix_paleyI(n) + elif skew_hadamard_matrix(n, existence=True) is True: + if existence: + return True + M = skew_hadamard_matrix(n, check=False) + elif regular_symmetric_hadamard_matrix_with_constant_diagonal(n, 1, existence=True) is True: + if existence: + return True + M = regular_symmetric_hadamard_matrix_with_constant_diagonal(n, 1) else: if existence: return Unknown From 86506ceecdec2c6e2b843409625b74aa741bae5b Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Wed, 19 Oct 2022 18:20:10 +0100 Subject: [PATCH 253/414] Fix failed test returning Unknown in hadamard_matrix --- src/sage/combinat/matrices/hadamard_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index e56722c50b3..839ba63fceb 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -405,7 +405,7 @@ def hadamard_matrix(n,existence=False, check=True): False sage: matrix.hadamard(12,existence=True) True - sage: matrix.hadamard(92,existence=True) + sage: matrix.hadamard(116,existence=True) Unknown sage: matrix.hadamard(10) Traceback (most recent call last): From ddc1f62c6d3dc34ab991d09ad227039d0dfdc632 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 23 Oct 2022 16:45:22 -0700 Subject: [PATCH 254/414] fusion ring updates to fmatrix, tests passing --- src/sage/algebras/fusion_rings/all.py | 2 +- src/sage/algebras/fusion_rings/fusion_ring.py | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/sage/algebras/fusion_rings/all.py b/src/sage/algebras/fusion_rings/all.py index e1dfaa1f2b2..e9125c55696 100644 --- a/src/sage/algebras/fusion_rings/all.py +++ b/src/sage/algebras/fusion_rings/all.py @@ -15,6 +15,6 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.algebras.fusion_rings.fusion_ring', ['FusionRing']) -lazy_import('sage.algebras.fusion_rings.f_matrix', ['FMatrix']) +# lazy_import('sage.algebras.fusion_rings.f_matrix', ['FMatrix']) del lazy_import diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index 029300b5d1f..becb5f4490e 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -571,7 +571,7 @@ def fvars_field(self): computed :func:`NumberField`. """ if self.is_multiplicity_free(): - return self.fmats.field() + return self.get_fmatrix().field() else: raise ValueError("Method is only available for multiplicity free fusion rings.") @@ -859,14 +859,16 @@ def s_ijconj(self, elt_i, elt_j, base_coercion=True): sage: E62.s_ij(e8, e1).conjugate() == E62.s_ijconj(e8, e1) True sage: F41 = FusionRing("F4", 1) - sage: F41.fmats.find_orthogonal_solution(verbose=False) + sage: fmats = F41.get_fmatrix() + sage: fmats.find_orthogonal_solution(verbose=False) sage: b = F41.basis() sage: all(F41.s_ijconj(x, y) == F41._basecoer(F41.s_ij(x, y, base_coercion=False).conjugate()) for x in b for y in b) True sage: G22 = FusionRing("G2", 2) - sage: G22.fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) + sage: fmats = G22.get_fmatrix() + sage: fmats.find_orthogonal_solution(verbose=False) # long time (~11 s) sage: b = G22.basis() # long time - sage: all(G22.s_ijconj(x, y) == G22.fmats.field()(G22.s_ij(x, y, base_coercion=False).conjugate()) for x in b for y in b) # long time + sage: all(G22.s_ijconj(x, y) == fmats.field()(G22.s_ij(x, y, base_coercion=False).conjugate()) for x in b for y in b) # long time True """ ret = self.s_ij(elt_i, elt_j, base_coercion=False).conjugate() @@ -1165,8 +1167,7 @@ def _get_trees(fr, top_row, root): comp_basis.extend(tuple([*top, *levels]) for levels in _get_trees(self, top_row, b)) return comp_basis - @lazy_attribute - def fmats(self): + def get_fmatrix(self, *args, **kwargs): r""" Construct an :class:`FMatrix` factory to solve the pentagon relations and organize the resulting F-symbols. @@ -1176,11 +1177,13 @@ def fmats(self): EXAMPLES:: sage: A15 = FusionRing("A1", 5) - sage: A15.fmats + sage: A15.get_fmatrix() F-Matrix factory for The Fusion Ring of Type A1 and level 5 with Integer Ring coefficients """ - from sage.algebras.fusion_rings.f_matrix import FMatrix - return FMatrix(self) + if not hasattr(self, 'fmats'): + from sage.algebras.fusion_rings.f_matrix import FMatrix + self.fmats = FMatrix(self, *args, **kwargs) + return self.fmats def _emap(self, mapper, input_args, worker_pool=None): r""" @@ -1210,12 +1213,14 @@ def _emap(self, mapper, input_args, worker_pool=None): sage: FR = FusionRing("A1", 4) sage: FR.fusion_labels(['idd', 'one', 'two', 'three', 'four'], inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: fmats = FR.get_fmatrix() + sage: fmats.find_orthogonal_solution(verbose=False) # long time sage: len(FR._emap('sig_2k', (1, one, one, 5))) # long time 13 sage: FR = FusionRing("A1", 2) sage: FR.fusion_labels("a", inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: fmats = FR.get_fmatrix() + sage: fmats.find_orthogonal_solution(verbose=False) sage: len(FR._emap('odd_one_out', (a1, a1, 7))) 16 """ @@ -1318,6 +1323,7 @@ def get_braid_generators(self, if n_strands < 3: raise ValueError("the number of strands must be an integer at least 3") # Construct associated FMatrix object and solve for F-symbols + self.get_fmatrix() if self.fmats._chkpt_status < 7: self.fmats.find_orthogonal_solution(checkpoint=checkpoint, save_results=save_results, From ddcfb48ffaf1cf0c5f79119bc9d9047bbbe2036c Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Sun, 23 Oct 2022 18:16:26 -0700 Subject: [PATCH 255/414] avoid importing FMatrix into global namespace... FMatrix accessible through FusionRing.get_fmatrix --- src/sage/algebras/fusion_rings/all.py | 1 - src/sage/algebras/fusion_rings/f_matrix.py | 154 +++++++++--------- src/sage/algebras/fusion_rings/fusion_ring.py | 6 +- 3 files changed, 84 insertions(+), 77 deletions(-) diff --git a/src/sage/algebras/fusion_rings/all.py b/src/sage/algebras/fusion_rings/all.py index e9125c55696..946524a7f43 100644 --- a/src/sage/algebras/fusion_rings/all.py +++ b/src/sage/algebras/fusion_rings/all.py @@ -15,6 +15,5 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.algebras.fusion_rings.fusion_ring', ['FusionRing']) -# lazy_import('sage.algebras.fusion_rings.f_matrix', ['FMatrix']) del lazy_import diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index f7ba23a588e..f63041abc63 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -152,7 +152,7 @@ class FMatrix(SageObject): sage: I = FusionRing("E8", 2, conjugate=True) sage: I.fusion_labels(["i0", "p", "s"], inject_variables=True) - sage: f = FMatrix(I, inject_variables=True); f + sage: f = I.get_fmatrix(inject_variables=True); f creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 F-Matrix factory for The Fusion Ring of Type E8 and level 2 with Integer Ring coefficients @@ -258,12 +258,12 @@ class FMatrix(SageObject): :: - sage: f = FMatrix(FusionRing("B3", 2)) + sage: f = FusionRing("B3", 2).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False, checkpoint=True) # not tested (~100 s) - sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # not tested + sage: all(v in CyclotomicField(56) for v in f.get_fvars().values()) # not tested True - sage: f = FMatrix(FusionRing("G2", 2)) + sage: f = FusionRing("G2", 2).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) # long time (~11 s) sage: f.field() # long time Algebraic Field @@ -274,7 +274,7 @@ def __init__(self, fusion_ring, fusion_label="f", var_prefix='fx', inject_variab EXAMPLES:: - sage: f = FMatrix(FusionRing("B3", 2)) + sage: f = FusionRing("B3", 2).get_fmatrix() sage: TestSuite(f).run(skip="_test_pickling") """ self._FR = fusion_ring @@ -312,7 +312,7 @@ def _repr_(self): EXAMPLES:: - sage: FMatrix(FusionRing("B2", 1)) + sage: FusionRing("B2", 1).get_fmatrix() F-Matrix factory for The Fusion Ring of Type B2 and level 1 with Integer Ring coefficients """ return "F-Matrix factory for %s"%self._FR @@ -323,7 +323,7 @@ def clear_equations(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("E6", 1)) + sage: f = FusionRing("E6", 1).get_fmatrix() sage: f.get_defining_equations('hexagons', output=False) sage: len(f.ideal_basis) 6 @@ -339,7 +339,7 @@ def clear_vars(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("C4", 1)) + sage: f = FusionRing("C4", 1).get_fmatrix() sage: fvars = f.get_fvars() sage: some_key = sorted(fvars)[0] sage: fvars[some_key] @@ -363,7 +363,7 @@ def _reset_solver_state(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).get_fmatrix() sage: f._reset_solver_state() sage: K = f.field() sage: len(f._nnz.nonzero_positions()) @@ -418,8 +418,9 @@ def fmat(self, a, b, c, d, x, y, data=True): EXAMPLES:: - sage: f=FMatrix(FusionRing("G2", 1, fusion_labels=("i0", "t"), inject_variables=True)) - sage: [f.fmat(t, t, t, t, x, y) for x in f._FR.basis() for y in f._FR.basis()] + sage: fr = FusionRing("G2", 1, fusion_labels=("i0", "t"), inject_variables=True) + sage: f = fr.get_fmatrix() + sage: [f.fmat(t, t, t, t, x, y) for x in fr.basis() for y in fr.basis()] [fx1, fx2, fx3, fx4] sage: f.find_cyclotomic_solution(output=True) Setting up hexagons and pentagons... @@ -479,7 +480,8 @@ def fmatrix(self, a, b, c, d): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 2, fusion_labels="c", inject_variables=True)) + sage: fr = FusionRing("A1", 2, fusion_labels="c", inject_variables=True) + sage: f = fr.get_fmatrix(new=True) sage: f.fmatrix(c1, c1, c1, c1) [fx0 fx1] [fx2 fx3] @@ -526,7 +528,7 @@ def field(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).get_fmatrix() sage: f.field() Cyclotomic Field of order 60 and degree 16 sage: f.find_orthogonal_solution(verbose=False) @@ -551,7 +553,7 @@ def FR(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3", 1)) + sage: f = FusionRing("D3", 1).get_fmatrix() sage: f.FR() The Fusion Ring of Type D3 and level 1 with Integer Ring coefficients """ @@ -567,7 +569,7 @@ def findcases(self, output=False): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1, fusion_labels=("i0", "t"))) + sage: f = FusionRing("G2", 1, fusion_labels=("i0", "t")).get_fmatrix() sage: f.findcases() 5 sage: f.findcases(output=True) @@ -614,7 +616,7 @@ def f_from(self, a, b, c, d): EXAMPLES:: sage: fr = FusionRing("A1", 3, fusion_labels="a", inject_variables=True) - sage: f = FMatrix(fr) + sage: f = fr.get_fmatrix() sage: f.fmatrix(a1, a1, a2, a2) [fx6 fx7] [fx8 fx9] @@ -639,7 +641,7 @@ def f_to(self, a, b, c, d): sage: b22 = FusionRing("B2", 2) sage: b22.fusion_labels("b", inject_variables=True) - sage: B=FMatrix(b22) + sage: B = b22.get_fmatrix() sage: B.fmatrix(b2, b4, b2, b4) [fx266 fx267 fx268] [fx269 fx270 fx271] @@ -668,7 +670,7 @@ def get_fvars(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) + sage: f = FusionRing("A2", 1).get_fmatrix(inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.get_fvars()[(f1, f1, f1, f0, f2, f2)] @@ -685,7 +687,7 @@ def get_poly_ring(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("B6", 1)) + sage: f = FusionRing("B6", 1).get_fmatrix() sage: f.get_poly_ring() Multivariate Polynomial Ring in fx0, ..., fx13 over Cyclotomic Field of order 96 and degree 32 @@ -710,13 +712,13 @@ def get_non_cyclotomic_roots(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("E6", 1)) + sage: f = FusionRing("E6", 1).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) sage: f.field() == f.FR().field() True sage: f.get_non_cyclotomic_roots() [] - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) sage: f.field() == f.FR().field() False @@ -744,7 +746,8 @@ def get_qqbar_embedding(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1), fusion_label="g", inject_variables=True) + sage: fr = FusionRing("G2", 1) + sage: f = fr.get_fmatrix(fusion_label="g", inject_variables=True, new=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: f.find_orthogonal_solution() @@ -777,7 +780,7 @@ def get_coerce_map_from_fr_cyclotomic_field(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) sage: f.FR().field() Cyclotomic Field of order 60 and degree 16 @@ -797,7 +800,7 @@ def get_coerce_map_from_fr_cyclotomic_field(self): :: - sage: f = FMatrix(FusionRing("A2", 1)) + sage: f = FusionRing("A2", 1).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) sage: phi = f.get_coerce_map_from_fr_cyclotomic_field() sage: f.field() @@ -828,7 +831,8 @@ def get_fvars_in_alg_field(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1), fusion_label="g", inject_variables=True) + sage: fr = FusionRing("G2", 1) + sage: f = fr.get_fmatrix(fusion_label="g", inject_variables=True, new=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: f.find_orthogonal_solution(verbose=False) @@ -849,7 +853,7 @@ def get_radical_expression(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).get_fmatrix() sage: f.FR().fusion_labels("g", inject_variables=True) sage: f.find_orthogonal_solution(verbose=False) sage: radical_fvars = f.get_radical_expression() # long time (~1.5s) @@ -869,7 +873,7 @@ def _get_known_vals(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D4", 1)) + sage: f = FusionRing("D4", 1).get_fmatrix() sage: f._reset_solver_state() sage: len(f._get_known_vals()) == 0 True @@ -890,7 +894,7 @@ def _get_known_nonz(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D5", 1)) # indirect doctest + sage: f = FusionRing("D5", 1).get_fmatrix() # indirect doctest sage: f._reset_solver_state() sage: f._nnz (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, @@ -911,7 +915,7 @@ def largest_fmat_size(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("B3", 2)) + sage: f = FusionRing("B3", 2).get_fmatrix() sage: f.largest_fmat_size() 4 """ @@ -938,7 +942,7 @@ def get_fvars_by_size(self, n, indices=False): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2", 2), inject_variables=True) + sage: f = FusionRing("A2", 2).get_fmatrix(inject_variables=True) creating variables fx1..fx287 Defining fx0, ..., fx286 sage: f.largest_fmat_size() @@ -997,14 +1001,14 @@ def save_fvars(self, filename): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2", 1)) + sage: f = FusionRing("A2", 1).get_fmatrix(new=True) sage: f.find_orthogonal_solution(verbose=False) sage: fvars = f.get_fvars() sage: K = f.field() sage: filename = f.get_fr_str() + "_solver_results.pickle" sage: f.save_fvars(filename) sage: del f - sage: f2 = FMatrix(FusionRing("A2", 1)) + sage: f2 = FusionRing("A2", 1).get_fmatrix(new=True) sage: f2.load_fvars(filename) sage: fvars == f2.get_fvars() True @@ -1029,14 +1033,14 @@ def load_fvars(self, filename): EXAMPLES:: - sage: f = FMatrix(FusionRing("A2", 1)) + sage: f = FusionRing("A2", 1).get_fmatrix(new=True) sage: f.find_orthogonal_solution(verbose=False) sage: fvars = f.get_fvars() sage: K = f.field() sage: filename = f.get_fr_str() + "_solver_results.pickle" sage: f.save_fvars(filename) sage: del f - sage: f2 = FMatrix(FusionRing("A2", 1)) + sage: f2 = FusionRing("A2", 1).get_fmatrix(new=True) sage: f2.load_fvars(filename) sage: fvars == f2.get_fvars() True @@ -1065,7 +1069,7 @@ def get_fr_str(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("B3", 1)) + sage: f = FusionRing("B3", 1).get_fmatrix() sage: f.get_fr_str() 'B31' """ @@ -1078,7 +1082,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 3)) + sage: f = FusionRing("A1", 3).get_fmatrix(new=True) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons', output=False) @@ -1093,7 +1097,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f._checkpoint(do_chkpt=True, status=2) Checkpoint 2 reached! sage: del f - sage: f = FMatrix(FusionRing("A1", 3)) + sage: f = FusionRing("A1", 3).get_fmatrix(new=True) sage: f.find_orthogonal_solution(warm_start="fmatrix_solver_checkpoint_A13.pickle") Computing F-symbols for The Fusion Ring of Type A1 and level 3 with Integer Ring coefficients with 71 variables... Set up 121 reduced pentagons... @@ -1111,7 +1115,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: sum(f._solved) == f._poly_ring.ngens() True sage: os.remove("fmatrix_solver_checkpoint_A13.pickle") - sage: f = FMatrix(FusionRing("A1", 2)) + sage: f = FusionRing("A1", 2).get_fmatrix(new=True) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons', output=False) @@ -1129,7 +1133,7 @@ def _checkpoint(self, do_chkpt, status, verbose=True): sage: f._checkpoint(do_chkpt=True, status=4) Checkpoint 4 reached! sage: del f - sage: f = FMatrix(FusionRing("A1", 2)) + sage: f = FusionRing("A1", 2).get_fmatrix(new=True) sage: f.find_orthogonal_solution(warm_start="fmatrix_solver_checkpoint_A12.pickle") Computing F-symbols for The Fusion Ring of Type A1 and level 2 with Integer Ring coefficients with 14 variables... Partitioned 0 equations into 0 components of size: @@ -1157,7 +1161,7 @@ def _restore_state(self, filename): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 2)) + sage: f = FusionRing("A1", 2).get_fmatrix(new=True) sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.get_defining_equations('hexagons', output=False) @@ -1177,7 +1181,7 @@ def _restore_state(self, filename): sage: f._checkpoint(do_chkpt=True, status=2) Checkpoint 2 reached! sage: del f - sage: f = FMatrix(FusionRing("A1", 2)) + sage: f = FusionRing("A1", 2).get_fmatrix(new=True) sage: f._reset_solver_state() sage: f._restore_state("fmatrix_solver_checkpoint_A12.pickle") sage: for sextuple, fvar in fvars.items(): @@ -1195,10 +1199,10 @@ def _restore_state(self, filename): TESTS:: - sage: f = FMatrix(FusionRing("A1", 3)) + sage: f = FusionRing("A1", 3).get_fmatrix(new=True) sage: f.find_orthogonal_solution(save_results="test.pickle", verbose=False) # long time sage: del f - sage: f = FMatrix(FusionRing("A1", 3)) + sage: f = FusionRing("A1", 3).get_fmatrix(new=True) sage: f.find_orthogonal_solution(warm_start="test.pickle") # long time sage: f._chkpt_status == 7 # long time True @@ -1250,7 +1254,7 @@ def start_worker_pool(self, processes=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).get_fmatrix(new=True) sage: f.start_worker_pool() sage: he = f.get_defining_equations('hexagons') sage: sorted(he) @@ -1328,7 +1332,7 @@ def shutdown_worker_pool(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 3)) + sage: f = FusionRing("A1", 3).get_fmatrix(new=True) sage: f.start_worker_pool() sage: he = f.get_defining_equations('hexagons') sage: f.shutdown_worker_pool() @@ -1365,7 +1369,7 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 2)) + sage: f = FusionRing("A1", 2).get_fmatrix() sage: f._reset_solver_state() sage: len(f._map_triv_reduce('get_reduced_hexagons', [(0, 1, False)])) 11 @@ -1424,7 +1428,7 @@ def get_orthogonality_constraints(self, output=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("B4", 1)) + sage: f = FusionRing("B4", 1).get_fmatrix() sage: f.get_orthogonality_constraints() [fx0^2 - 1, fx1^2 - 1, @@ -1480,7 +1484,7 @@ def get_defining_equations(self, option, output=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("B2", 1)) + sage: f = FusionRing("B2", 1).get_fmatrix() sage: sorted(f.get_defining_equations('hexagons')) [fx7 + 1, fx6 - 1, @@ -1528,10 +1532,10 @@ def _tup_to_fpoly(self, eq_tup): EXAMPLES:: - sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: f = FMatrix(FusionRing("C3", 1)) + sage: f = FusionRing("C3", 1).get_fmatrix() sage: f.start_worker_pool() sage: he = f.get_defining_equations('hexagons') + sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: all(f._tup_to_fpoly(poly_to_tup(h)) for h in he) True sage: f.shutdown_worker_pool() @@ -1544,7 +1548,7 @@ def _update_reduction_params(self, eqns=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 3)) + sage: f = FusionRing("A1", 3).get_fmatrix() sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.start_worker_pool() @@ -1579,7 +1583,7 @@ def _triangular_elim(self, eqns=None, verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3", 1)) + sage: f = FusionRing("D3", 1).get_fmatrix() sage: f.get_defining_equations('hexagons', output=False) sage: f.get_orthogonality_constraints(output=False) sage: gb = f._par_graph_gb(verbose=False) @@ -1659,7 +1663,7 @@ def equations_graph(self, eqns=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("A3", 1)) + sage: f = FusionRing("A3", 1).get_fmatrix() sage: f.get_poly_ring().ngens() 27 sage: he = f.get_defining_equations('hexagons') @@ -1703,7 +1707,7 @@ def _partition_eqns(self, eqns=None, verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("C2", 1)) + sage: f = FusionRing("C2", 1).get_fmatrix() sage: f.get_defining_equations('hexagons', output=False) sage: partition = f._partition_eqns() Partitioned 11 equations into 5 components of size: @@ -1751,7 +1755,7 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb EXAMPLES:: - sage: f = FMatrix(FusionRing("F4", 1)) + sage: f = FusionRing("F4", 1).get_fmatrix() sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f.start_worker_pool() @@ -1796,7 +1800,7 @@ def _get_component_variety(self, var, eqns): EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 2)) + sage: f = FusionRing("G2", 2).get_fmatrix(new=True) sage: f.start_worker_pool() sage: f.get_defining_equations('hexagons', output=False) # long time sage: f.shutdown_worker_pool() @@ -1848,10 +1852,10 @@ def attempt_number_field_computation(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("F4", 2)) + sage: f = FusionRing("F4", 2).get_fmatrix() sage: f.attempt_number_field_computation() False - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).get_fmatrix() sage: f.attempt_number_field_computation() True @@ -1895,8 +1899,8 @@ def _get_explicit_solution(self, eqns=None, verbose=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 3)) # indirect doctest - sage: f.find_orthogonal_solution() # long time + sage: f = FusionRing("A1", 3).get_fmatrix() # indirect doctest + sage: f.find_orthogonal_solution() # long time Computing F-symbols for The Fusion Ring of Type A1 and level 3 with Integer Ring coefficients with 71 variables... Set up 134 hex and orthogonality constraints... Partitioned 134 equations into 17 components of size: @@ -2052,7 +2056,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start EXAMPLES:: - sage: f = FMatrix(FusionRing("B5", 1), fusion_label="b", inject_variables=True) + sage: f = FusionRing("B5", 1).get_fmatrix(fusion_label="b", inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: f.find_orthogonal_solution() @@ -2105,9 +2109,6 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start # Loading from a pickle with solved F-symbols if self._chkpt_status > 5: return - # loads_shared_memory = False - # if use_mp: - # loads_shared_memory = self.start_worker_pool() if use_mp: self.start_worker_pool() if verbose: @@ -2191,10 +2192,10 @@ def _fix_gauge(self, algorithm=""): EXAMPLES:: - sage: f = FMatrix(FusionRing("A3", 1)) - sage: f._reset_solver_state() # long time + sage: f = FusionRing("A3", 1).get_fmatrix() + sage: f._reset_solver_state() # long time sage: f._var_to_sextuple = {f._poly_ring.gen(i): s for i, s in f._idx_to_sextuple.items()} # long time - sage: eqns = f.get_defining_equations("hexagons")+f.get_defining_equations("pentagons") # long time + sage: eqns = f.get_defining_equations("hexagons")+f.get_defining_equations("pentagons") # long time sage: f.ideal_basis = set(Ideal(eqns).groebner_basis()) # long time sage: _, _ = f._substitute_degree_one() # long time sage: f._fix_gauge() # long time @@ -2224,7 +2225,8 @@ def _substitute_degree_one(self, eqns=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) + sage: fr = FusionRing("D3", 1) + sage: f = fr.get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -2264,7 +2266,8 @@ def _update_equations(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) + sage: fr = FusionRing("D3", 1) + sage: f = fr.get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -2301,7 +2304,8 @@ def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, o EXAMPLES:: - sage: f = FMatrix(FusionRing("A2", 1, fusion_labels="a", inject_variables=True), inject_variables=True) + sage: fr = FusionRing("A2", 1, fusion_labels="a", inject_variables=True) + sage: f = fr.get_fmatrix(inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.find_cyclotomic_solution(output=True) @@ -2369,7 +2373,7 @@ def fmats_are_orthogonal(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("D4", 1)) + sage: f = FusionRing("D4", 1).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) sage: f.fmats_are_orthogonal() True @@ -2386,7 +2390,7 @@ def fvars_are_real(self): EXAMPLES:: - sage: f = FMatrix(FusionRing("A1", 3)) + sage: f = FusionRing("A1", 3).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) # long time sage: f.fvars_are_real() # not tested (cypari issue in doctesting framework) True @@ -2415,8 +2419,8 @@ def certify_pentagons(self, use_mp=True, verbose=False): EXAMPLES:: - sage: f = FMatrix(FusionRing("C3", 1)) - sage: f.find_orthogonal_solution() # long time + sage: f = FusionRing("C3", 1).get_fmatrix() + sage: f.find_orthogonal_solution() # long time Computing F-symbols for The Fusion Ring of Type C3 and level 1 with Integer Ring coefficients with 71 variables... Set up 134 hex and orthogonality constraints... Partitioned 134 equations into 17 components of size: @@ -2434,7 +2438,7 @@ def certify_pentagons(self, use_mp=True, verbose=False): Partitioned 6 equations into 6 components of size: [1, 1, 1, 1, 1, 1] Computing appropriate NumberField... - sage: f.certify_pentagons() is None # not tested (long time ~1.5s, cypari issue in doctesting framework) + sage: f.certify_pentagons() is None # not tested (long time ~1.5s, cypari issue in doctesting framework) True """ fvars_copy = deepcopy(self._fvars) diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index becb5f4490e..f392e8b42c2 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -1180,7 +1180,11 @@ def get_fmatrix(self, *args, **kwargs): sage: A15.get_fmatrix() F-Matrix factory for The Fusion Ring of Type A1 and level 5 with Integer Ring coefficients """ - if not hasattr(self, 'fmats'): + # Initialize fresh FMatrix object. Useful if you need to reset + # FMatrix properties and there are various FusionRing objects (unique) + # associated to same level and algebra. + if not hasattr(self, 'fmats') or kwargs.get('new', False): + kwargs.pop('new', None) from sage.algebras.fusion_rings.f_matrix import FMatrix self.fmats = FMatrix(self, *args, **kwargs) return self.fmats From ec342042b933ed457cf22246d77cdd7a10f724b2 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Mon, 24 Oct 2022 09:38:53 +0200 Subject: [PATCH 256/414] docstring fixes for Payley constructions --- src/sage/combinat/matrices/hadamard_matrix.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 839ba63fceb..0344c4ade32 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -93,8 +93,8 @@ def hadamard_matrix_paleyI(n, normalize=True): r""" Implement the Paley type I construction. - The Paley type I case corresponds to the case `p \cong 3 \mod{4}` for a - prime `p` (see [Hora]_). + The Paley type I case corresponds to the case `p=n-1 \cong 3 \mod{4}` for a + prime power `p` (see [Hora]_). INPUT: @@ -160,8 +160,8 @@ def hadamard_matrix_paleyII(n): r""" Implement the Paley type II construction. - The Paley type II case corresponds to the case `p \cong 1 \mod{4}` for a - prime `p` (see [Hora]_). + The Paley type II case corresponds to the case `p=n/2-1 \cong 1 \mod{4}` for a + prime power `p` (see [Hora]_). EXAMPLES:: From aa6105d9bc4d1e51857110cd8147878e1800caf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 24 Oct 2022 10:18:38 +0200 Subject: [PATCH 257/414] minor tweaks in the doc --- src/sage/combinat/triangles_FHM.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/triangles_FHM.py b/src/sage/combinat/triangles_FHM.py index 2a9b08b0791..ad9fa36891c 100644 --- a/src/sage/combinat/triangles_FHM.py +++ b/src/sage/combinat/triangles_FHM.py @@ -6,6 +6,9 @@ possibly with other parameters. The conversion methods amount to specific invertible rational change-of-variables involving `x` and `y`. +These polynomial are called triangles because their supports, the sets +of exponents where their coefficients can be non-zero, have a triangular shape. + The M-triangle class is motivated by the generating series of Möbius numbers for graded posets. A typical example is:: @@ -697,8 +700,8 @@ def h(self): .. MATH:: - H(x,y) = (1+x)^d \sum_{0\leq i; 0\leq j \leq d-2i} gamma_{i,j} - (\frac{x}{(1+x)^2})^i (\frac{1+xy}{1+x})^j + H(x,y) = (1+x)^d \sum_{0\leq i; 0\leq j \leq d-2i} \gamma_{i,j} + \left(\frac{x}{(1+x)^2}\right)^i \left(\frac{1+xy}{1+x}\right)^j EXAMPLES:: From f5be12f4e5c0ee462c0036c674d4e87fb2f52e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 24 Oct 2022 10:30:27 +0200 Subject: [PATCH 258/414] provide "an_element" for Compositions --- src/sage/combinat/composition.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index ab51d83ecfd..5cb2cf73281 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -1924,6 +1924,18 @@ def zero(self): """ return Composition([]) + def _an_element_(self): + """ + Return an element of ``self``. + + EXAMPLES:: + + sage: C = Compositions() + sage: C.an_element() + [] + """ + return self.zero() + def __iter__(self): """ Iterate over all compositions. From 9acf7643c3999d616ae100df9e4aa86b9cc1cb23 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Mon, 24 Oct 2022 15:35:17 +0200 Subject: [PATCH 259/414] =?UTF-8?q?fix=20parent=20of=200=C3=970=20companio?= =?UTF-8?q?n=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/matrix/special.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/sage/matrix/special.py b/src/sage/matrix/special.py index 619d5d7c892..5d3bb13445b 100644 --- a/src/sage/matrix/special.py +++ b/src/sage/matrix/special.py @@ -67,6 +67,7 @@ import sage.matrix.matrix_space as matrix_space from sage.modules.free_module_element import vector from sage.structure.element import is_Matrix +from sage.structure.sequence import Sequence from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.integer import Integer @@ -2332,6 +2333,9 @@ def companion_matrix(poly, format='right'): ... ValueError: polynomial cannot be specified by an empty list + sage: companion_matrix([QQ.one()]).parent() + Full MatrixSpace of 0 by 0 dense matrices over Rational Field + AUTHOR: - Rob Beezer (2011-05-19) @@ -2340,7 +2344,7 @@ def companion_matrix(poly, format='right'): if format not in ['right', 'left', 'top', 'bottom']: raise ValueError("format must be 'right', 'left', 'top' or 'bottom', not {0}".format(format)) try: - poly = list(poly) + poly = Sequence(poly) except TypeError: raise TypeError('input must be a polynomial (not a symbolic expression, see docstring), or other iterable, not {0}'.format(poly)) n = len(poly) - 1 @@ -2348,31 +2352,30 @@ def companion_matrix(poly, format='right'): raise ValueError('polynomial cannot be specified by an empty list') if not poly[n] == 1: raise ValueError('polynomial (or the polynomial implied by coefficients) must be monic, not a leading coefficient of {0}'.format(poly[n])) - entries = [0] * (n * n) + try: + M = sage.matrix.constructor.matrix(poly.universe(), n, n) + except TypeError: + raise TypeError("unable to find common ring for coefficients from polynomial") # 1's below diagonal, or above diagonal if format in ['right', 'top']: for i in range(n - 1): - entries[(i+1)*n + i] = 1 + M[i+1, i] = 1 else: for i in range(n-1): - entries[i*n + i+1] = 1 + M[i, i+1] = 1 # right side, left side (reversed), bottom edge, top edge (reversed) if format == 'right': for i in range(n): - entries[i*n + n-1] = -poly[i] + M[i, n-1] = -poly[i] elif format == 'left': for i in range(n): - entries[(n-1-i)*n + 0] = -poly[i] + M[n-1-i, 0] = -poly[i] elif format == 'bottom': for i in range(n): - entries[(n-1)*n + i] = -poly[i] + M[n-1, i] = -poly[i] elif format == 'top': for i in range(n): - entries[0*n + n-1-i] = -poly[i] - try: - M = sage.matrix.constructor.matrix(n, n, entries) - except TypeError: - raise TypeError("unable to find common ring for coefficients from polynomial") + M[0, n-1-i] = -poly[i] return M From 79eccefc7a488e2f42cfbd2c5676c6b5ad52f6cb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 24 Oct 2022 14:39:54 -0700 Subject: [PATCH 260/414] build/make/Makefile.in: Only warn if meson_python testsuite fails --- build/make/Makefile.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/make/Makefile.in b/build/make/Makefile.in index 9148f3d7577..e971def2416 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -443,8 +443,9 @@ $(foreach pkgname, $(NORMAL_PACKAGES) $(SCRIPT_PACKAGES),\ # # Since Python's self-tests seem to fail on all platforms, we disable # its test suite by default. +# meson_python 0.10.0 fails on some platforms, so we reduce it to warnings. # However, if SAGE_CHECK=warn, we do not do that. -SAGE_CHECK_PACKAGES_DEFAULT_yes := !python3 +SAGE_CHECK_PACKAGES_DEFAULT_yes := !python3,?meson_python SAGE_CHECK_PACKAGES_DEFAULT_warn := SAGE_CHECK_PACKAGES_DEFAULT_no := comma := , From 8139c367dba1e9ac88703289feb4910e47b9dc7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 25 Oct 2022 08:18:40 +0200 Subject: [PATCH 261/414] rephrase to "must use" --- src/sage/combinat/composition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 5cb2cf73281..97b6bf58a2c 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -1555,7 +1555,7 @@ class Compositions(UniqueRepresentation, Parent): ... ValueError: setting min_part=0 is not allowed for Compositions - Preferably consider using ``IntegerVectors`` instead:: + Instead you must use ``IntegerVectors``:: sage: list(IntegerVectors(2, 3)) [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] From af2f7625c5f5a79020f3039bc546a79d42049f47 Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Tue, 25 Oct 2022 21:29:03 -0600 Subject: [PATCH 262/414] improvements suggested by reviewer --- src/sage/combinat/permutation.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 0fa32de70ee..4a43a727b3f 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7498,17 +7498,18 @@ def from_cycles(n, cycles, parent=None): if parent is None: parent = Permutations(n) - # None represents a value of the permutation that has not yet been specified + # None represents a value of the permutation that has not been specified yet p = n * [None] for cycle in cycles: - for i in range(len(cycle)): + cycle_length = len(cycle) + for i in range(cycle_length): # two consecutive terms in the cycle represent k and p(k) - k = cycle[i] - pk = cycle[(i + 1) % len(cycle)] + k = ZZ(cycle[i]) + pk = ZZ(cycle[(i + 1) % cycle_length]) # check that the values are valid - if (int(k) < 1) or (int(pk) < 1): + if (k < 1) or (pk < 1): raise ValueError("all elements should be strictly positive " f"integers, but I found {min(k, pk)}") if (k > n) or (pk > n): @@ -7519,10 +7520,10 @@ def from_cycles(n, cycles, parent=None): " in the input") p[k - 1] = pk - # unspecified values are fixed points of the permutation + # values that are not in any cycle are fixed points of the permutation for i in range(n): if p[i] is None: - p[i] = i + 1 + p[i] = ZZ(i + 1) return parent(p, check_input=False) def from_lehmer_code(lehmer, parent=None): From 0300962bf65a087a765ac6c897ed0811a314d15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 27 Oct 2022 08:47:35 +0200 Subject: [PATCH 263/414] minor fixes in doc in 2 pyx files --- src/sage/rings/finite_rings/finite_field_base.pyx | 2 +- src/sage/rings/tate_algebra_ideal.pyx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index c91126649f1..1ab1ac08eaa 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -457,7 +457,7 @@ cdef class FiniteField(Field): ``self.0`` to the unique element of ``im_gens`` is a valid field homomorphism. Otherwise, return ``False``. - EXAMPLES:: + EXAMPLES: Between prime fields:: diff --git a/src/sage/rings/tate_algebra_ideal.pyx b/src/sage/rings/tate_algebra_ideal.pyx index 8826f67d000..e0b40cd1e7a 100644 --- a/src/sage/rings/tate_algebra_ideal.pyx +++ b/src/sage/rings/tate_algebra_ideal.pyx @@ -292,7 +292,7 @@ class TateAlgebraIdeal(Ideal_generic): All ideals are saturated when `\pi` is invertible. - EXAMPLES:: + EXAMPLES: Over classical Tate algebras (where `\pi` is invertible), this method always returns ``True``:: @@ -350,7 +350,7 @@ class TateAlgebraIdeal(Ideal_generic): When `\pi` is invertible in `A`, all ideals are saturated. - EXAMPLES:: + EXAMPLES: Over classical Tate algebras (where `\pi` is invertible), this method always returns the same ideal:: From 01e18432b8c98c57b6c82b11de97bc3ba099b39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 27 Oct 2022 08:54:49 +0200 Subject: [PATCH 264/414] more fixes in doc --- src/sage/geometry/polyhedron/base5.py | 4 ++-- src/sage/graphs/distances_all_pairs.pyx | 2 +- src/sage/graphs/generators/world_map.py | 2 +- src/sage/matrix/matrix_sparse.pyx | 2 +- src/sage/rings/polynomial/ore_polynomial_element.pyx | 2 +- src/sage/rings/tate_algebra_element.pyx | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index 7a77f9685a3..04d1fa0314b 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -190,9 +190,9 @@ def polar(self, in_affine_span=False): sage: (P*point).polar(in_affine_span=True) == P.polar()*point True - TESTS:: + TESTS: - Check that :trac:`25081` is fixed:: + Check that :trac:`25081` is fixed:: sage: C = polytopes.hypercube(4,backend='cdd') sage: C.polar().backend() diff --git a/src/sage/graphs/distances_all_pairs.pyx b/src/sage/graphs/distances_all_pairs.pyx index 7da899e88b8..87e83b4f5ce 100644 --- a/src/sage/graphs/distances_all_pairs.pyx +++ b/src/sage/graphs/distances_all_pairs.pyx @@ -1441,7 +1441,7 @@ cdef uint32_t diameter_DiFUB(short_digraph sd, - ``source`` -- starting node of the first BFS - TESTS:: + TESTS: The diameter of a weakly connected digraph is infinity :: diff --git a/src/sage/graphs/generators/world_map.py b/src/sage/graphs/generators/world_map.py index 2b0e30686cc..42d0a95372f 100644 --- a/src/sage/graphs/generators/world_map.py +++ b/src/sage/graphs/generators/world_map.py @@ -311,7 +311,7 @@ def WorldMap(): sage: sorted(g.connected_component_containing_vertex('Ireland')) ['Ireland', 'United Kingdom'] - TESTS:: + TESTS: :trac:`24488`:: diff --git a/src/sage/matrix/matrix_sparse.pyx b/src/sage/matrix/matrix_sparse.pyx index cd5daef1903..dcd9c2e1550 100644 --- a/src/sage/matrix/matrix_sparse.pyx +++ b/src/sage/matrix/matrix_sparse.pyx @@ -1005,7 +1005,7 @@ cdef class Matrix_sparse(matrix.Matrix): [ 0 1 2 3] [ 4 5 6 7] - TESTS:: + TESTS: One can stack matrices over different rings (:trac:`16399`). :: diff --git a/src/sage/rings/polynomial/ore_polynomial_element.pyx b/src/sage/rings/polynomial/ore_polynomial_element.pyx index 858ec02cbfa..a5cade9325f 100644 --- a/src/sage/rings/polynomial/ore_polynomial_element.pyx +++ b/src/sage/rings/polynomial/ore_polynomial_element.pyx @@ -2735,7 +2735,7 @@ cdef class OrePolynomial_generic_dense(OrePolynomial): sage: P * Q == Q * P False - TESTS:: + TESTS: We check associativity and distributivity:: diff --git a/src/sage/rings/tate_algebra_element.pyx b/src/sage/rings/tate_algebra_element.pyx index 3f09e3722d3..02b8305cd17 100644 --- a/src/sage/rings/tate_algebra_element.pyx +++ b/src/sage/rings/tate_algebra_element.pyx @@ -3502,7 +3502,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): - ``other`` -- a Tate series - TESTS:: + TESTS: We check that the S-polynomial of two monomials vanishes:: From 247faf7ee4a5a2a0848d77b6765e327e5116e636 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Fri, 28 Oct 2022 16:04:01 +0900 Subject: [PATCH 265/414] Being more careful about passing the coefficients. --- src/sage/rings/lazy_series_ring.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index b7b1953f52f..c505d1417df 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1535,6 +1535,14 @@ def q_pochhammer(self, q=None): sage: phi[:20] == M.euler()[:20] True + TESTS:: + + sage: R = ZZ['q'].fraction_field() + sage: q = R.gen() + sage: L. = LazyLaurentSeriesRing(LazyDirichletSeriesRing(R, "s")) + sage: z.q_pochhammer(q) + 1 + ((1/(q-1)))*z + ((q/(q^3-q^2-q+1)))*z^2 + ... + O(z^7) + REFERENCES: - :wikipedia:`Q-Pochhammer_symbol` @@ -1550,7 +1558,7 @@ def q_pochhammer(self, q=None): one = qP.one() def coeff(n): return (-1)**n * q**binomial(n, 2) / qP.prod(one - q**i for i in range(1, n+1)) - return self(coeff, valuation=0) + return self(coefficients=coeff, valuation=0) def euler(self): r""" @@ -1578,6 +1586,12 @@ def euler(self): sage: P[:20] == [Partitions(n).cardinality() for n in range(20)] True + TESTS:: + + sage: L. = LazyLaurentSeriesRing(LazyDirichletSeriesRing(QQ, "s")) + sage: q.euler() + 1 - q - q^2 + q^5 + O(q^7) + REFERENCES: - :wikipedia:`Euler_function` @@ -1588,7 +1602,8 @@ def coeff(n): if rem: return ZZ.zero() return (-1) ** ((m + 1) // 6) - return self(coeff, valuation=0) + return self(coefficients=coeff, valuation=0) + ###################################################################### From 87b4d8ec4a315a31bfa51a7a40eb5d52b603217d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 28 Oct 2022 11:51:33 +0200 Subject: [PATCH 266/414] some details about OUTPUT and INPUT in the doc --- src/sage/algebras/free_algebra_quotient.py | 12 +++-- .../algebras/steenrod/steenrod_algebra.py | 37 +++++++++---- .../steenrod/steenrod_algebra_bases.py | 6 ++- .../steenrod/steenrod_algebra_misc.py | 52 +++++++++++++------ .../covariant_functorial_construction.py | 1 + src/sage/categories/coxeter_groups.py | 6 ++- .../examples/finite_coxeter_groups.py | 5 +- .../examples/finite_enumerated_sets.py | 2 + src/sage/categories/finite_monoids.py | 4 +- src/sage/categories/groups.py | 2 +- src/sage/categories/magmas.py | 1 + src/sage/categories/poor_man_map.py | 2 + .../combinat/fully_commutative_elements.py | 4 +- src/sage/combinat/matrices/latin.py | 14 ++--- src/sage/combinat/root_system/cartan_type.py | 21 +++++--- .../root_system/root_lattice_realizations.py | 6 ++- src/sage/combinat/words/words.py | 7 +-- src/sage/crypto/lattice.py | 6 ++- src/sage/geometry/lattice_polytope.py | 27 +++++----- src/sage/groups/generic.py | 6 ++- src/sage/probability/random_variable.py | 7 ++- .../rings/multi_power_series_ring_element.py | 6 ++- src/sage/rings/polynomial/toy_buchberger.py | 4 +- .../hyperelliptic_curves/jacobian_morphism.py | 12 +++-- src/sage/structure/formal_sum.py | 15 +++--- src/sage/topology/simplicial_set.py | 8 ++- 26 files changed, 179 insertions(+), 94 deletions(-) diff --git a/src/sage/algebras/free_algebra_quotient.py b/src/sage/algebras/free_algebra_quotient.py index d5f8050a1c9..a338123e9a6 100644 --- a/src/sage/algebras/free_algebra_quotient.py +++ b/src/sage/algebras/free_algebra_quotient.py @@ -169,9 +169,9 @@ def _element_constructor_(self, x): sage: a = H._element_constructor_([1,2,3,4]); a 1 + 2*i + 3*j + 4*k """ - return self.element_class(self,x) + return self.element_class(self, x) - def _coerce_map_from_(self,S): + def _coerce_map_from_(self,S ): """ EXAMPLES:: @@ -331,11 +331,13 @@ def hamilton_quatalg(R): constructed as a free algebra quotient. INPUT: - - R -- a commutative ring + + - R -- a commutative ring OUTPUT: - - Q -- quaternion algebra - - gens -- generators for Q + + - Q -- quaternion algebra + - gens -- generators for Q EXAMPLES:: diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index 8feec0d6d98..ea2fb868c52 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -525,7 +525,9 @@ def __init__(self, p=2, basis='milnor', **kwds): - ``precision`` - (optional, default ``None``) - ``generic`` - (optional, default 'auto') - OUTPUT: mod `p` Steenrod algebra with basis, or a sub-Hopf + OUTPUT: + + mod `p` Steenrod algebra with basis, or a sub-Hopf algebra of the mod `p` Steenrod algebra defined by the given profile function. @@ -1036,8 +1038,9 @@ def homogeneous_component(self, n): - `n` - integer - OUTPUT: a vector space spanned by the basis for this algebra - in dimension `n` + OUTPUT: + + a vector space spanned by the basis for this algebra in dimension `n` EXAMPLES:: @@ -1128,7 +1131,9 @@ def product_on_basis(self, t1, t2): - ``t1``, ``t2`` -- tuples, the indices of two basis elements of self - OUTPUT: the product of the two corresponding basis elements, + OUTPUT: + + the product of the two corresponding basis elements, as an element of self ALGORITHM: If the two elements are represented in the Milnor @@ -1251,7 +1256,9 @@ def coproduct_on_basis(self, t, algorithm=None): Which of these methods is used is controlled by whether ``algorithm`` is 'milnor' or 'serre-cartan'. - OUTPUT: the coproduct of the corresponding basis element, + OUTPUT: + + the coproduct of the corresponding basis element, as an element of ``self`` tensor ``self``. EXAMPLES:: @@ -1433,7 +1440,9 @@ def antipode_on_basis(self, t): - ``t`` -- tuple, the index of a basis element of self - OUTPUT: the antipode of the corresponding basis element, + OUTPUT: + + the antipode of the corresponding basis element, as an element of self. ALGORITHM: according to a result of Milnor's, the antipode of @@ -2184,7 +2193,9 @@ def basis(self, d=None): - `d` -- integer or ``None``, optional (default ``None``) - OUTPUT: If `d` is ``None``, then return a basis of the algebra. + OUTPUT: + + If `d` is ``None``, then return a basis of the algebra. Otherwise, return the basis in degree `d`. EXAMPLES:: @@ -2301,7 +2312,9 @@ def P(self, *nums): - ``a, b, c, ...`` - non-negative integers - OUTPUT: element of the Steenrod algebra given by the Milnor + OUTPUT: + + element of the Steenrod algebra given by the Milnor single basis element `P(a, b, c, ...)` Note that at the prime 2, this is the same element as @@ -3794,7 +3807,9 @@ def SteenrodAlgebra(p=2, basis='milnor', generic='auto', **kwds): - ``precision`` - integer or ``None`` (optional, default ``None``) - ``generic`` - (optional, default 'auto') - OUTPUT: mod `p` Steenrod algebra or one of its sub-Hopf algebras, + OUTPUT: + + mod `p` Steenrod algebra or one of its sub-Hopf algebras, elements of which are printed using ``basis`` See below for information about ``basis``, ``profile``, etc. @@ -4166,7 +4181,9 @@ def AA(n=None, p=2): - `n` - non-negative integer, optional (default ``None``) - `p` - prime number, optional (default 2) - OUTPUT: If `n` is ``None``, then return the full Steenrod algebra. + OUTPUT: + + If `n` is ``None``, then return the full Steenrod algebra. Otherwise, return `A(n)`. When `p=2`, `A(n)` is the sub-Hopf algebra generated by the diff --git a/src/sage/algebras/steenrod/steenrod_algebra_bases.py b/src/sage/algebras/steenrod/steenrod_algebra_bases.py index 4cfbe2f83e4..f028e6d5df5 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_bases.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_bases.py @@ -193,8 +193,9 @@ def convert_from_milnor_matrix(n, basis, p=2, generic='auto'): - ``p`` - positive prime number (optional, default 2) - OUTPUT: ``matrix`` - change-of-basis matrix, a square matrix over - GF(p) + OUTPUT: + + ``matrix`` - change-of-basis matrix, a square matrix over GF(p) .. note:: @@ -1082,6 +1083,7 @@ def sorting_pair(s,t,basis): # pair used for sorting the basis result.append((tuple(q_mono), tuple(p_mono))) return tuple(result) + ############################################################################# def steenrod_basis_error_check(dim, p, **kwds): """ diff --git a/src/sage/algebras/steenrod/steenrod_algebra_misc.py b/src/sage/algebras/steenrod/steenrod_algebra_misc.py index 850081f6e67..d8fedad70b8 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_misc.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_misc.py @@ -311,7 +311,9 @@ def normalize_profile(profile, precision=None, truncation_type='auto', p=2, gene - `p` - prime, optional, default 2 - `generic` - boolean, optional, default ``None`` - OUTPUT: a triple ``profile, precision, truncation_type``, in + OUTPUT: + + a triple ``profile, precision, truncation_type``, in standard form as described below. The "standard form" is as follows: ``profile`` should be a tuple @@ -638,6 +640,7 @@ def milnor_mono_to_string(mono, latex=False, generic=False): string = string + ")" return string.strip(" ") + def serre_cartan_mono_to_string(mono, latex=False, generic=False): r""" String representation of element of the Serre-Cartan basis. @@ -730,7 +733,9 @@ def wood_mono_to_string(mono, latex=False): - ``latex`` - boolean (optional, default False), if true, output LaTeX string - OUTPUT: ``string`` - concatenation of strings of the form + OUTPUT: + + ``string`` - concatenation of strings of the form ``Sq^{2^s (2^{t+1}-1)}`` for each pair (s,t) EXAMPLES:: @@ -773,8 +778,9 @@ def wall_mono_to_string(mono, latex=False): - ``latex`` - boolean (optional, default False), if true, output LaTeX string - OUTPUT: ``string`` - concatenation of strings ``Q^{m}_{k}`` for - each pair (m,k) + OUTPUT: + + ``string`` - concatenation of strings ``Q^{m}_{k}`` for each pair (m,k) EXAMPLES:: @@ -812,8 +818,9 @@ def wall_long_mono_to_string(mono, latex=False): - ``latex`` - boolean (optional, default False), if true, output LaTeX string - OUTPUT: ``string`` - concatenation of strings of the form - ``Sq^(2^m)`` + OUTPUT: + + ``string`` - concatenation of strings of the form ``Sq^(2^m)`` EXAMPLES:: @@ -855,8 +862,10 @@ def arnonA_mono_to_string(mono, latex=False, p=2): - ``latex`` - boolean (optional, default False), if true, output LaTeX string - OUTPUT: ``string`` - concatenation of strings of the form - ``X^{m}_{k}`` for each pair (m,k) + OUTPUT: + + ``string`` - concatenation of strings of the form ``X^{m}_{k}`` + for each pair (m,k) EXAMPLES:: @@ -894,8 +903,9 @@ def arnonA_long_mono_to_string(mono, latex=False, p=2): - ``latex`` - boolean (optional, default False), if true, output LaTeX string - OUTPUT: ``string`` - concatenation of strings of the form - ``Sq(2^m)`` + OUTPUT: + + ``string`` - concatenation of strings of the form ``Sq(2^m)`` EXAMPLES:: @@ -939,8 +949,10 @@ def pst_mono_to_string(mono, latex=False, generic=False): - ``generic`` - whether to format generically, or for the prime 2 (default) - OUTPUT: ``string`` - concatenation of strings of the form - ``P^{s}_{t}`` for each pair (s,t) + OUTPUT: + + ``string`` - concatenation of strings of the form ``P^{s}_{t}`` + for each pair (s,t) EXAMPLES:: @@ -999,8 +1011,10 @@ def comm_mono_to_string(mono, latex=False, generic=False): - ``generic`` - whether to format generically, or for the prime 2 (default) - OUTPUT: ``string`` - concatenation of strings of the form - ``c_{s,t}`` for each pair (s,t) + OUTPUT: + + ``string`` - concatenation of strings of the form ``c_{s,t}`` + for each pair (s,t) EXAMPLES:: @@ -1059,8 +1073,10 @@ def comm_long_mono_to_string(mono, p, latex=False, generic=False): - ``generic`` - whether to format generically, or for the prime 2 (default) - OUTPUT: ``string`` - concatenation of strings of the form ``s_{2^s - ... 2^(s+t-1)}`` for each pair (s,t) + OUTPUT: + + ``string`` - concatenation of strings of the form ``s_{2^s... 2^(s+t-1)}`` + for each pair (s,t) EXAMPLES:: @@ -1121,7 +1137,9 @@ def convert_perm(m): - ``m`` - tuple of non-negative integers with no repetitions - OUTPUT: ``list`` - conversion of ``m`` to a permutation of the set + OUTPUT: + + ``list`` - conversion of ``m`` to a permutation of the set 1,2,...,len(m) If ``m=(3,7,4)``, then one can view ``m`` as representing the diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index 4ed3a678986..813b66e8afd 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -205,6 +205,7 @@ def __call__(self, args, **kwargs): Functorial construction application INPUT: + - ``self``: a covariant functorial construction `F` - ``args``: a tuple (or iterable) of parents or elements diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index c4f4cdab349..b7ee2ac9fe9 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -263,8 +263,10 @@ def braid_orbit(self, word): - ``word``: a list (or iterable) of indices in ``self.index_set()`` - OUTPUT: a list of all lists that can be obtained from - ``word`` by replacements of braid relations + OUTPUT: + + a list of all lists that can be obtained from + ``word`` by replacements of braid relations See :meth:`braid_relations` for the definition of braid relations. diff --git a/src/sage/categories/examples/finite_coxeter_groups.py b/src/sage/categories/examples/finite_coxeter_groups.py index c135c877b70..de49a789fa0 100644 --- a/src/sage/categories/examples/finite_coxeter_groups.py +++ b/src/sage/categories/examples/finite_coxeter_groups.py @@ -87,10 +87,11 @@ class DihedralGroup(UniqueRepresentation, Parent): def __init__(self, n=5): r""" + Construct the `n`-th DihedralGroup of order `2 n` + INPUT: - - ``n`` - an integer with `n>=2` - Construct the n-th DihedralGroup of order 2*n + - `n` -- an integer with `n>=2` EXAMPLES:: diff --git a/src/sage/categories/examples/finite_enumerated_sets.py b/src/sage/categories/examples/finite_enumerated_sets.py index 91896c77d25..24222b4fa91 100644 --- a/src/sage/categories/examples/finite_enumerated_sets.py +++ b/src/sage/categories/examples/finite_enumerated_sets.py @@ -146,6 +146,7 @@ def ambient(self): def lift(self, x): """ INPUT: + - ``x`` -- an element of ``self`` Lifts ``x`` to the ambient space for ``self``, as per @@ -164,6 +165,7 @@ def lift(self, x): def retract(self, x): """ INPUT: + - ``x`` -- an element of the ambient space for ``self`` Retracts ``x`` from the ambient space to ``self``, as per diff --git a/src/sage/categories/finite_monoids.py b/src/sage/categories/finite_monoids.py index c58cfc3e4e8..a300339ae22 100644 --- a/src/sage/categories/finite_monoids.py +++ b/src/sage/categories/finite_monoids.py @@ -35,7 +35,9 @@ def nerve(self): r""" The nerve (classifying space) of this monoid. - OUTPUT: the nerve `BG` (if `G` denotes this monoid), as a + OUTPUT: + + the nerve `BG` (if `G` denotes this monoid), as a simplicial set. The `k`-dimensional simplices of this object are indexed by products of `k` elements in the monoid: diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index ef681dbb07a..97d57799f8c 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -233,11 +233,11 @@ def cayley_table(self, names='letters', elements=None): this can be used when the base set is infinite. OUTPUT: + An object representing the multiplication table. This is an :class:`~sage.matrix.operation_table.OperationTable` object and even more documentation can be found there. - EXAMPLES: Permutation groups, matrix groups and abelian groups diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index 801d3700707..936ada2a40e 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -857,6 +857,7 @@ def multiplication_table(self, names='letters', elements=None): this can be used when the base set is infinite. OUTPUT: + The multiplication table as an object of the class :class:`~sage.matrix.operation_table.OperationTable` which defines several methods for manipulating and diff --git a/src/sage/categories/poor_man_map.py b/src/sage/categories/poor_man_map.py index 507705fb9f9..13f8322f8d3 100644 --- a/src/sage/categories/poor_man_map.py +++ b/src/sage/categories/poor_man_map.py @@ -14,6 +14,7 @@ # **************************************************************************** import sage.structure.sage_object + class PoorManMap(sage.structure.sage_object.SageObject): """ A class for maps between sets which are not (yet) modeled by parents @@ -184,6 +185,7 @@ def __mul__(self, other): Composition INPUT: + - ``self`` -- a map `f` - ``other`` -- a map `g` diff --git a/src/sage/combinat/fully_commutative_elements.py b/src/sage/combinat/fully_commutative_elements.py index b8f26d8ba8b..bb46692a8b7 100644 --- a/src/sage/combinat/fully_commutative_elements.py +++ b/src/sage/combinat/fully_commutative_elements.py @@ -214,7 +214,9 @@ def heap(self, **kargs): - ``display_labeling`` -- boolean (default: False). Setting the value to True will display the label `s_i` for each element `i` of the poset - OUTPUT: A labeled poset where the underlying set is `\{0,1,...,k-1\}` + OUTPUT: + + A labeled poset where the underlying set is `\{0,1,...,k-1\}` and where each element `i` carries `s_i` as its label. The partial order `\prec` on the poset is defined by declaring `i\prec j` if `i = ` diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py index 7167fe2208a..2ba280b545a 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py @@ -339,6 +339,7 @@ def cantor_composition(D1,D2,f,h,genus): a = a.monic() return (a, b) + class JacobianMorphism_divisor_class_field(AdditiveGroupElement, SchemeMorphism): r""" An element of a Jacobian defined over a field, i.e. in @@ -348,10 +349,13 @@ def __init__(self, parent, polys, check=True): r""" Create a new Jacobian element in Mumford representation. - INPUT: parent: the parent Homset polys: Mumford's `u` and - `v` polynomials check (default: True): if True, ensure that - polynomials define a divisor on the appropriate curve and are - reduced + INPUT: + + - parent -- the parent Homset + - polys -- Mumford's `u` and `v` polynomials + - check (default: ``True``) -- if ``True``, ensure that + polynomials define a divisor on the appropriate curve and are + reduced .. warning:: diff --git a/src/sage/structure/formal_sum.py b/src/sage/structure/formal_sum.py index 0e14ab6dd1e..a96d3bb3351 100644 --- a/src/sage/structure/formal_sum.py +++ b/src/sage/structure/formal_sum.py @@ -85,13 +85,14 @@ class FormalSum(ModuleElement): def __init__(self, x, parent=None, check=True, reduce=True): """ INPUT: - - ``x`` -- object - - ``parent`` -- FormalSums(R) module (default: FormalSums(ZZ)) - - ``check`` -- bool (default: True) if False, might not coerce - coefficients into base ring, which can speed - up constructing a formal sum. - - ``reduce`` -- reduce (default: True) if False, do not - combine common terms + + - ``x`` -- object + - ``parent`` -- FormalSums(R) module (default: FormalSums(ZZ)) + - ``check`` -- bool (default: ``True``) if ``False``, might not coerce + coefficients into base ring, which can speed + up constructing a formal sum. + - ``reduce`` -- reduce (default: ``True``) if ``False``, do not + combine common terms EXAMPLES:: diff --git a/src/sage/topology/simplicial_set.py b/src/sage/topology/simplicial_set.py index 836a13da05d..0ca2ff5bcb2 100644 --- a/src/sage/topology/simplicial_set.py +++ b/src/sage/topology/simplicial_set.py @@ -3819,7 +3819,9 @@ def standardize_degeneracies(*L): - ``L`` -- list of integers, representing a composition of degeneracies in a simplicial set. - OUTPUT: an equivalent list of degeneracies, standardized to be + OUTPUT: + + an equivalent list of degeneracies, standardized to be written in decreasing order, using the simplicial identity .. MATH:: @@ -3923,7 +3925,9 @@ def standardize_face_maps(*L): - ``L`` -- list of integers, representing a composition of face maps in a simplicial set. - OUTPUT: an equivalent list of face maps, standardized to be + OUTPUT: + + an equivalent list of face maps, standardized to be written in non-increasing order, using the simplicial identity .. MATH:: From fdfa523d3b6444cd92e751f0d2f90ae2981276f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 28 Oct 2022 16:49:23 +0200 Subject: [PATCH 267/414] a few more details --- src/sage/groups/generic.py | 4 ++-- src/sage/rings/polynomial/omega.py | 3 +-- src/sage/rings/polynomial/polynomial_fateman.py | 12 +++++++----- src/sage/schemes/elliptic_curves/period_lattice.py | 1 + 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index e1c64f4159d..d5823ebf9e0 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -693,11 +693,11 @@ def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, i OUTPUT: - Returns an integer `n` such that `b^n = a` (or `nb = a`), + This returns an integer `n` such that `b^n = a` (or `nb = a`), assuming that ``ord`` is a multiple of the order of the base `b`. If ``ord`` is not specified, an attempt is made to compute it. - If no such `n` exists, this function raises a ValueError exception. + If no such `n` exists, this function raises a ``ValueError`` exception. .. warning:: diff --git a/src/sage/rings/polynomial/omega.py b/src/sage/rings/polynomial/omega.py index 97b97d4fe8c..3421ef04126 100644 --- a/src/sage/rings/polynomial/omega.py +++ b/src/sage/rings/polynomial/omega.py @@ -358,8 +358,7 @@ def _simplify_(numerator, terms): OUTPUT: A pair of a Laurent polynomial and a tuple of Laurent polynomials - representing numerator and denominator as described in the - INPUT-section. + representing numerator and denominator as described in the INPUT-section. EXAMPLES:: diff --git a/src/sage/rings/polynomial/polynomial_fateman.py b/src/sage/rings/polynomial/polynomial_fateman.py index 3b3246f2d47..0b21f42169d 100644 --- a/src/sage/rings/polynomial/polynomial_fateman.py +++ b/src/sage/rings/polynomial/polynomial_fateman.py @@ -19,7 +19,9 @@ def _mul_fateman_to_int2(f_list, g_list): """ Convert a polynomial to an integer by evaluating it + INPUT: p, a list of integers + OUTPUT: padding """ max_coeff_f = max([abs(i) for i in f_list]) @@ -28,13 +30,13 @@ def _mul_fateman_to_int2(f_list, g_list): return int(pyceil(pylog(b, 2))) -def _mul_fateman_to_poly(number,padding): +def _mul_fateman_to_poly(number, padding): """ - Converts a number to a polynomial, according - to a padding - OUTPUT: a list containing the coefficient of - a polynomial of degree len(list) + Converts a number to a polynomial, according to a padding + + OUTPUT: + a list containing the coefficient of a polynomial of degree len(list) """ coeffs = [] flag=0 diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 85c76603092..7206ea74a7a 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -1941,6 +1941,7 @@ def normalise_periods(w1, w2): def extended_agm_iteration(a, b, c): r""" Internal function for the extended AGM used in elliptic logarithm computation. + INPUT: - ``a``, ``b``, ``c`` (real or complex) -- three real or complex numbers. From 80c84f743abadd42f956a7514f4255f0cd060699 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 28 Oct 2022 14:18:19 -0700 Subject: [PATCH 268/414] updating shm_managers to new FMatrix interface --- .../algebras/fusion_rings/shm_managers.pyx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 5a90595f6e6..223f4925761 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -76,7 +76,7 @@ cdef class KSHandler: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: # Create shared data structure - sage: f = FMatrix(FusionRing("A1", 2), inject_variables=True) + sage: f = FusionRing("A1", 2).f_matrix(inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() @@ -104,7 +104,7 @@ cdef class KSHandler: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: # Create shared data structure - sage: f = FMatrix(FusionRing("A1", 2), inject_variables=True) + sage: f = FusionRing("A1", 2).f_matrix(inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() @@ -182,7 +182,7 @@ cdef class KSHandler: EXAMPLES:: - sage: f = FMatrix(FusionRing("B5", 1)) + sage: f = FusionRing("B5", 1).f_matrix() sage: f._reset_solver_state() sage: for idx, sq in f._ks.items(): ....: k @@ -281,7 +281,7 @@ cdef class KSHandler: TESTS:: - sage: f = FMatrix(FusionRing("C2", 2)) + sage: f = FusionRing("C2", 2).f_matrix() sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: from sage.algebras.fusion_rings.shm_managers import KSHandler @@ -304,7 +304,7 @@ cdef class KSHandler: TESTS:: - sage: f = FMatrix(FusionRing("A3", 1)) + sage: f = FusionRing("A3", 1).f_matrix() sage: f._reset_solver_state() sage: loads(dumps(f._ks)) == f._ks True @@ -321,7 +321,7 @@ cdef class KSHandler: EXAMPLES:: - sage: f = FMatrix(FusionRing("A3", 1)) + sage: f = FusionRing("A3", 1).f_matrix() sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f._ks.update(f.ideal_basis) @@ -348,7 +348,7 @@ def make_KSHandler(n_slots, field, init_data): TESTS:: - sage: f = FMatrix(FusionRing("B4", 1)) + sage: f = FusionRing("B4", 1).f_matrix() sage: f._reset_solver_state() sage: loads(dumps(f._ks)) == f._ks # indirect doctest True @@ -433,7 +433,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: # Create shared data structure - sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) + sage: f = FusionRing("A2", 1).f_matrix(inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.start_worker_pool() @@ -460,7 +460,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: # Create shared data structure - sage: f = FMatrix(FusionRing("A2", 1), inject_variables=True) + sage: f = FusionRing("A2", 1).f_matrix(inject_variables=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.start_worker_pool() @@ -528,7 +528,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: f = FMatrix(FusionRing("B7", 1), inject_variables=True) + sage: f = FusionRing("B7", 1).f_matrix(inject_variables=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: f.start_worker_pool() @@ -630,7 +630,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: f = FMatrix(FusionRing("A3", 1), inject_variables=True) + sage: f = FusionRing("A3", 1).f_matrix(inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f.start_worker_pool() @@ -711,7 +711,7 @@ cdef class FvarsHandler: TESTS:: - sage: f = FMatrix(FusionRing("F4", 1)) + sage: f = FusionRing("F4", 1).f_matrix() sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() @@ -739,7 +739,7 @@ cdef class FvarsHandler: EXAMPLES:: - sage: f = FMatrix(FusionRing("G2", 1), inject_variables=True) + sage: f = FusionRing("G2", 1).f_matrix(inject_variables=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler @@ -759,7 +759,7 @@ def make_FvarsHandler(n, field, idx_map, init_data): TESTS:: - sage: f = FMatrix(FusionRing("G2", 1)) + sage: f = FusionRing("G2", 1).f_matrix() sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() From 10b5e117de06e1700a304192743bbb72ab970de0 Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 28 Oct 2022 14:21:30 -0700 Subject: [PATCH 269/414] updating pyx files to use new FMatrix interface --- .../algebras/fusion_rings/fast_parallel_fmats_methods.pyx | 8 ++++---- src/sage/algebras/fusion_rings/poly_tup_engine.pyx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx index f68fca95369..a768e49a24b 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx @@ -39,7 +39,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) + sage: f = FusionRing("D3", 1).f_matrix(inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -113,7 +113,7 @@ cpdef _backward_subs(factory, bint flatten=True): EXAMPLES:: - sage: f = FMatrix(FusionRing("D3", 1), inject_variables=True) + sage: f = FusionRing("D3", 1).f_matrix(inject_variables=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -480,12 +480,12 @@ cpdef executor(tuple params): TESTS:: sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import executor - sage: fmats = FMatrix(FusionRing("A1", 3)) + sage: fmats = FusionRing("A1", 3).f_matrix() sage: fmats._reset_solver_state() sage: params = (('get_reduced_hexagons', id(fmats)), (0, 1, True)) sage: len(executor(params)) == 63 True - sage: fmats = FMatrix(FusionRing("E6", 1)) + sage: fmats = FusionRing("E6", 1).f_matrix() sage: fmats._reset_solver_state() sage: params = (('get_reduced_hexagons', id(fmats)), (0, 1, False)) sage: len(executor(params)) == 6 diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx index e14be4106d6..0817c14799f 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -99,7 +99,7 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): EXAMPLES:: sage: from sage.algebras.fusion_rings.poly_tup_engine import _unflatten_coeffs - sage: fm = FMatrix(FusionRing("A2", 2)) + sage: fm = FusionRing("A2", 2).f_matrix() sage: p = fm._poly_ring.random_element() sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: flat_poly_tup = list() From 696742cd60aa43ff158417a0ef09cb7e000b4f3b Mon Sep 17 00:00:00 2001 From: Willie Aboumrad Date: Fri, 28 Oct 2022 14:51:08 -0700 Subject: [PATCH 270/414] pyx files now use updated FMatrix interface --- .../fast_parallel_fmats_methods.pyx | 8 +++--- .../fast_parallel_fusion_ring_braid_repn.pyx | 14 +++++----- .../algebras/fusion_rings/poly_tup_engine.pyx | 2 +- .../algebras/fusion_rings/shm_managers.pyx | 28 +++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx index a768e49a24b..bc1ad78e826 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx @@ -39,7 +39,7 @@ cpdef _solve_for_linear_terms(factory, list eqns=None): EXAMPLES:: - sage: f = FusionRing("D3", 1).f_matrix(inject_variables=True) + sage: f = FusionRing("D3", 1).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -113,7 +113,7 @@ cpdef _backward_subs(factory, bint flatten=True): EXAMPLES:: - sage: f = FusionRing("D3", 1).f_matrix(inject_variables=True) + sage: f = FusionRing("D3", 1).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f._reset_solver_state() @@ -480,12 +480,12 @@ cpdef executor(tuple params): TESTS:: sage: from sage.algebras.fusion_rings.fast_parallel_fmats_methods import executor - sage: fmats = FusionRing("A1", 3).f_matrix() + sage: fmats = FusionRing("A1", 3).get_fmatrix() sage: fmats._reset_solver_state() sage: params = (('get_reduced_hexagons', id(fmats)), (0, 1, True)) sage: len(executor(params)) == 63 True - sage: fmats = FusionRing("E6", 1).f_matrix() + sage: fmats = FusionRing("E6", 1).get_fmatrix() sage: fmats._reset_solver_state() sage: params = (('get_reduced_hexagons', id(fmats)), (0, 1, False)) sage: len(executor(params)) == 6 diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx index 1b0ae45e6d0..c68bcd2e88c 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx @@ -29,7 +29,7 @@ cdef mid_sig_ij(fusion_ring, row, col, a, b): This method assumes F-matrices are orthogonal. """ # Pre-compute common parameters for efficiency - _fvars = fusion_ring.fmats._fvars + _fvars = fusion_ring.get_fmatrix()._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() @@ -59,7 +59,7 @@ cdef odd_one_out_ij(fusion_ring, xi, xj, a, b): This method assumes F-matrices are orthogonal. """ # Pre-compute common parameters for efficiency - _fvars = fusion_ring.fmats._fvars + _fvars = fusion_ring.get_fmatrix()._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() @@ -103,7 +103,7 @@ cdef sig_2k(fusion_ring, tuple args): Compute entries of the `2k`-th braid generator """ # Pre-compute common parameters for efficiency - _fvars = fusion_ring.fmats._fvars + _fvars = fusion_ring.get_fmatrix()._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() @@ -183,7 +183,7 @@ cdef odd_one_out(fusion_ring, tuple args): odd number of strands. """ # Pre-compute common parameters for efficiency - _fvars = fusion_ring.fmats._fvars + _fvars = fusion_ring.get_fmatrix()._fvars _Nk_ij = fusion_ring.Nk_ij one = fusion_ring.one() @@ -281,14 +281,14 @@ cpdef executor(tuple params): sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import executor sage: FR = FusionRing("A1", 4) sage: FR.fusion_labels(['idd', 'one', 'two', 'three', 'four'], inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) # long time + sage: FR.get_fmatrix().find_orthogonal_solution(verbose=False) # long time sage: params = (('sig_2k', id(FR)), (0, 1, (1, one, one, 5))) # long time sage: len(executor(params)) == 13 # long time True sage: from sage.algebras.fusion_rings.fast_parallel_fusion_ring_braid_repn import executor sage: FR = FusionRing("A1", 2) sage: FR.fusion_labels("a", inject_variables=True) - sage: FR.fmats.find_orthogonal_solution(verbose=False) + sage: FR.get_fmatrix().find_orthogonal_solution(verbose=False) sage: params = (('odd_one_out', id(FR)), (0, 1, (a2, a2, 5))) sage: len(executor(params)) == 1 True @@ -323,7 +323,7 @@ cpdef _unflatten_entries(fusion_ring, list entries): True """ F = fusion_ring.fvars_field() - fm = fusion_ring.fmats + fm = fusion_ring.get_fmatrix() if F != QQbar: for i, (coord, entry) in enumerate(entries): entries[i] = (coord, F(entry)) diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx index 0817c14799f..8255e463492 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -99,7 +99,7 @@ cpdef tuple _unflatten_coeffs(field, tuple eq_tup): EXAMPLES:: sage: from sage.algebras.fusion_rings.poly_tup_engine import _unflatten_coeffs - sage: fm = FusionRing("A2", 2).f_matrix() + sage: fm = FusionRing("A2", 2).get_fmatrix() sage: p = fm._poly_ring.random_element() sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup sage: flat_poly_tup = list() diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 223f4925761..b5c443d7cc2 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -76,7 +76,7 @@ cdef class KSHandler: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: # Create shared data structure - sage: f = FusionRing("A1", 2).f_matrix(inject_variables=True) + sage: f = FusionRing("A1", 2).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() @@ -104,7 +104,7 @@ cdef class KSHandler: sage: from sage.algebras.fusion_rings.shm_managers import KSHandler sage: # Create shared data structure - sage: f = FusionRing("A1", 2).f_matrix(inject_variables=True) + sage: f = FusionRing("A1", 2).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: n = f._poly_ring.ngens() @@ -182,7 +182,7 @@ cdef class KSHandler: EXAMPLES:: - sage: f = FusionRing("B5", 1).f_matrix() + sage: f = FusionRing("B5", 1).get_fmatrix() sage: f._reset_solver_state() sage: for idx, sq in f._ks.items(): ....: k @@ -281,7 +281,7 @@ cdef class KSHandler: TESTS:: - sage: f = FusionRing("C2", 2).f_matrix() + sage: f = FusionRing("C2", 2).get_fmatrix() sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: from sage.algebras.fusion_rings.shm_managers import KSHandler @@ -304,7 +304,7 @@ cdef class KSHandler: TESTS:: - sage: f = FusionRing("A3", 1).f_matrix() + sage: f = FusionRing("A3", 1).get_fmatrix() sage: f._reset_solver_state() sage: loads(dumps(f._ks)) == f._ks True @@ -321,7 +321,7 @@ cdef class KSHandler: EXAMPLES:: - sage: f = FusionRing("A3", 1).f_matrix() + sage: f = FusionRing("A3", 1).get_fmatrix() sage: f._reset_solver_state() sage: f.get_orthogonality_constraints(output=False) sage: f._ks.update(f.ideal_basis) @@ -348,7 +348,7 @@ def make_KSHandler(n_slots, field, init_data): TESTS:: - sage: f = FusionRing("B4", 1).f_matrix() + sage: f = FusionRing("B4", 1).get_fmatrix() sage: f._reset_solver_state() sage: loads(dumps(f._ks)) == f._ks # indirect doctest True @@ -433,7 +433,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: # Create shared data structure - sage: f = FusionRing("A2", 1).f_matrix(inject_variables=True) + sage: f = FusionRing("A2", 1).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.start_worker_pool() @@ -460,7 +460,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: # Create shared data structure - sage: f = FusionRing("A2", 1).f_matrix(inject_variables=True) + sage: f = FusionRing("A2", 1).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx8 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7 sage: f.start_worker_pool() @@ -528,7 +528,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: f = FusionRing("B7", 1).f_matrix(inject_variables=True) + sage: f = FusionRing("B7", 1).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx14 Defining fx0, fx1, fx2, fx3, fx4, fx5, fx6, fx7, fx8, fx9, fx10, fx11, fx12, fx13 sage: f.start_worker_pool() @@ -630,7 +630,7 @@ cdef class FvarsHandler: sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: from sage.algebras.fusion_rings.poly_tup_engine import poly_to_tup - sage: f = FusionRing("A3", 1).f_matrix(inject_variables=True) + sage: f = FusionRing("A3", 1).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx27 Defining fx0, ..., fx26 sage: f.start_worker_pool() @@ -711,7 +711,7 @@ cdef class FvarsHandler: TESTS:: - sage: f = FusionRing("F4", 1).f_matrix() + sage: f = FusionRing("F4", 1).get_fmatrix() sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() @@ -739,7 +739,7 @@ cdef class FvarsHandler: EXAMPLES:: - sage: f = FusionRing("G2", 1).f_matrix(inject_variables=True) + sage: f = FusionRing("G2", 1).get_fmatrix(inject_variables=True, new=True) creating variables fx1..fx5 Defining fx0, fx1, fx2, fx3, fx4 sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler @@ -759,7 +759,7 @@ def make_FvarsHandler(n, field, idx_map, init_data): TESTS:: - sage: f = FusionRing("G2", 1).f_matrix() + sage: f = FusionRing("G2", 1).get_fmatrix() sage: from sage.algebras.fusion_rings.shm_managers import FvarsHandler sage: n = f._poly_ring.ngens() sage: f.start_worker_pool() From 7fdda638f1b51472be81d25a66d7c9d70615eebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 29 Oct 2022 11:30:25 +0200 Subject: [PATCH 271/414] fix suggested details --- src/sage/categories/examples/finite_coxeter_groups.py | 11 +++++------ .../categories/examples/finite_enumerated_sets.py | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sage/categories/examples/finite_coxeter_groups.py b/src/sage/categories/examples/finite_coxeter_groups.py index de49a789fa0..ad37c96e667 100644 --- a/src/sage/categories/examples/finite_coxeter_groups.py +++ b/src/sage/categories/examples/finite_coxeter_groups.py @@ -1,14 +1,13 @@ r""" Examples of finite Coxeter groups """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 Nicolas M. Thiery # Copyright (C) 2009 Nicolas Borie # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** - +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.misc.cachefunc import cached_method from sage.structure.parent import Parent from sage.structure.element_wrapper import ElementWrapper @@ -87,11 +86,11 @@ class DihedralGroup(UniqueRepresentation, Parent): def __init__(self, n=5): r""" - Construct the `n`-th DihedralGroup of order `2 n` + Construct the `n`-th DihedralGroup of order `2 n`. INPUT: - - `n` -- an integer with `n>=2` + - `n` -- an integer with `n>=2` EXAMPLES:: diff --git a/src/sage/categories/examples/finite_enumerated_sets.py b/src/sage/categories/examples/finite_enumerated_sets.py index 24222b4fa91..6a606f7b9e3 100644 --- a/src/sage/categories/examples/finite_enumerated_sets.py +++ b/src/sage/categories/examples/finite_enumerated_sets.py @@ -147,7 +147,7 @@ def lift(self, x): """ INPUT: - - ``x`` -- an element of ``self`` + - ``x`` -- an element of ``self`` Lifts ``x`` to the ambient space for ``self``, as per :meth:`Sets.Subquotients.ParentMethods.lift() @@ -166,7 +166,7 @@ def retract(self, x): """ INPUT: - - ``x`` -- an element of the ambient space for ``self`` + - ``x`` -- an element of the ambient space for ``self`` Retracts ``x`` from the ambient space to ``self``, as per :meth:`Sets.Subquotients.ParentMethods.retract() From 7fc01deff4eb6c2d17bc3785126e07d7ed88c3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 29 Oct 2022 17:47:32 +0200 Subject: [PATCH 272/414] more fixes --- src/sage/algebras/free_algebra_quotient.py | 16 +++++++++------- .../categories/examples/finite_coxeter_groups.py | 9 +++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/sage/algebras/free_algebra_quotient.py b/src/sage/algebras/free_algebra_quotient.py index a338123e9a6..583eb5f9ae8 100644 --- a/src/sage/algebras/free_algebra_quotient.py +++ b/src/sage/algebras/free_algebra_quotient.py @@ -171,7 +171,7 @@ def _element_constructor_(self, x): """ return self.element_class(self, x) - def _coerce_map_from_(self,S ): + def _coerce_map_from_(self, S): """ EXAMPLES:: @@ -183,7 +183,7 @@ def _coerce_map_from_(self,S ): sage: H._coerce_map_from_(GF(7)) False """ - return S==self or self.__free_algebra.has_coerce_map_from(S) + return S == self or self.__free_algebra.has_coerce_map_from(S) def _repr_(self): """ @@ -197,7 +197,7 @@ def _repr_(self): n = self.__ngens r = self.__module.dimension() x = self.variable_names() - return "Free algebra quotient on %s generators %s and dimension %s over %s"%(n,x,r,R) + return "Free algebra quotient on %s generators %s and dimension %s over %s" % (n, x, r, R) def gen(self, i): """ @@ -361,8 +361,10 @@ def hamilton_quatalg(R): A = FreeAlgebra(R, n, 'i') F = A.monoid() i, j, k = F.gens() - mons = [ F(1), i, j, k ] - M = MatrixSpace(R,4) - mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]), M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]), M([0,0,0,1, 0,0,-1,0, 0,1,0,0, -1,0,0,0]) ] - H3 = FreeAlgebraQuotient(A,mons,mats, names=('i','j','k')) + mons = [F(1), i, j, k] + M = MatrixSpace(R, 4) + mats = [M([0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0]), + M([0, 0, 1, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, -1, 0, 0]), + M([0, 0, 0, 1, 0, 0, -1, 0, 0, 1, 0, 0, -1, 0, 0, 0])] + H3 = FreeAlgebraQuotient(A, mons, mats, names=('i', 'j', 'k')) return H3, H3.gens() diff --git a/src/sage/categories/examples/finite_coxeter_groups.py b/src/sage/categories/examples/finite_coxeter_groups.py index ad37c96e667..71ca54c8a5b 100644 --- a/src/sage/categories/examples/finite_coxeter_groups.py +++ b/src/sage/categories/examples/finite_coxeter_groups.py @@ -90,7 +90,7 @@ def __init__(self, n=5): INPUT: - - `n` -- an integer with `n>=2` + - `n` -- an integer with `n \geq 2` EXAMPLES:: @@ -238,12 +238,13 @@ def apply_simple_reflection_right(self, i): return self.parent()(reduced_word[:-1]) else: return self.parent()(reduced_word[1:]) - elif (len(reduced_word) == n-1 and (not self.has_descent(i))) and (reduced_word[0] == 2): - return self.parent()((1,)+reduced_word) + elif (len(reduced_word) == n - 1 and (not self.has_descent(i))) and (reduced_word[0] == 2): + return self.parent()((1,) + reduced_word) else: if self.has_descent(i): return self.parent()(reduced_word[:-1]) else: - return self.parent()(reduced_word+(i,)) + return self.parent()(reduced_word + (i,)) + Example = DihedralGroup From ed9fb97c733c1c3e3cf7cc01526aebc06990a378 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 30 Oct 2022 14:52:28 +0900 Subject: [PATCH 273/414] Fixing some last little details. --- src/sage/algebras/fusion_rings/f_matrix.py | 11 +++++++---- src/sage/algebras/fusion_rings/fusion_ring.py | 16 ++++++++-------- src/sage/combinat/root_system/weyl_characters.py | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index f63041abc63..618bf40812a 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -1300,6 +1300,7 @@ class methods. fvar_names = self._shared_fvars.shm.name # Initialize worker pool processes args = (id(self), s_name, vd_name, ks_names, fvar_names, n_proc, pids_name) + def init(fmats_id, solved_name, vd_name, ks_names, fvar_names, n_proc, pids_name): """ Connect worker process to shared memory resources @@ -1380,12 +1381,12 @@ def _map_triv_reduce(self, mapper, input_iter, worker_pool=None, chunksize=None, sage: f.shutdown_worker_pool() """ if mp_thresh is None: - mp_thresh = self.mp_thresh + mp_thresh = self.mp_thresh # Compute multiprocessing parameters if worker_pool is not None: try: n = len(input_iter) - except: + except (TypeError, ValueError, AttributeError): n = mp_thresh + 1 if chunksize is None: chunksize = n // (worker_pool._processes**2) + 1 @@ -1675,7 +1676,8 @@ def equations_graph(self, eqns=None): eqns = self.ideal_basis G = Graph() - if not eqns: return G + if not eqns: + return G # Eqns could be a list of poly objects or poly tuples stored in internal repn if isinstance(eqns[0], tuple): @@ -1773,7 +1775,8 @@ def _par_graph_gb(self, eqns=None, term_order="degrevlex", largest_comp=45, verb fx3^2 + (zeta80^24 - zeta80^16)] sage: f.shutdown_worker_pool() """ - if eqns is None: eqns = self.ideal_basis + if eqns is None: + eqns = self.ideal_basis small_comps = list() temp_eqns = list() for comp, comp_eqns in self._partition_eqns(eqns=eqns, verbose=verbose).items(): diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index f392e8b42c2..a4e27c3534a 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -2,13 +2,14 @@ Fusion Rings """ # **************************************************************************** -# Copyright (C) 2019 Daniel Bump -# Guillermo Aboumrad -# Travis Scrimshaw -# Nicolas Thiery +# Copyright (C) 2019 Daniel Bump +# Guillermo Aboumrad +# Travis Scrimshaw +# Nicolas Thiery +# 2022 Guillermo Aboumrad # -# Distributed under the terms of the GNU General Public License (GPL) -# https://www.gnu.org/licenses/ +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ # **************************************************************************** from itertools import product, zip_longest @@ -22,7 +23,6 @@ from sage.matrix.constructor import matrix from sage.matrix.special import diagonal_matrix from sage.misc.cachefunc import cached_method -from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc import inject_variable from sage.rings.integer_ring import ZZ from sage.rings.number_field.number_field import CyclotomicField @@ -405,7 +405,7 @@ def test_braid_representation(self, max_strands=6, anyon=None): True """ if not self.is_multiplicity_free(): # Braid group representation is not available if self is not multiplicity free - raise NotImplementedError("only implemented for multiplicity free fusion rings") + raise NotImplementedError("only implemented for multiplicity free fusion rings") b = self.basis() results = [] # Test with different numbers of strands diff --git a/src/sage/combinat/root_system/weyl_characters.py b/src/sage/combinat/root_system/weyl_characters.py index 56163c5ca85..036f8a58614 100644 --- a/src/sage/combinat/root_system/weyl_characters.py +++ b/src/sage/combinat/root_system/weyl_characters.py @@ -133,7 +133,7 @@ def __init__(self, ct, base_ring=ZZ, prefix=None, style="lattice", k=None, conju self._basecoer = None self._k = k if k is not None: - self._k = Integer(k) + self._k = Integer(k) if ct.is_irreducible(): self._opposition = ct.opposition_automorphism() self._highest = self._space.highest_root() From 589a5b86e6d67a3738ba595f7b62a0a2af004b6a Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 30 Oct 2022 15:07:46 +0900 Subject: [PATCH 274/414] Just a few more polish. --- src/sage/algebras/fusion_rings/all.py | 1 + src/sage/algebras/fusion_rings/f_matrix.py | 36 +++++++++---------- .../fast_parallel_fmats_methods.pyx | 3 +- .../fast_parallel_fusion_ring_braid_repn.pyx | 2 +- src/sage/algebras/fusion_rings/fusion_ring.py | 28 +++++++-------- .../algebras/fusion_rings/poly_tup_engine.pxd | 1 + .../algebras/fusion_rings/poly_tup_engine.pyx | 2 +- .../algebras/fusion_rings/shm_managers.pyx | 7 ++-- 8 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/sage/algebras/fusion_rings/all.py b/src/sage/algebras/fusion_rings/all.py index 946524a7f43..4a7b2196938 100644 --- a/src/sage/algebras/fusion_rings/all.py +++ b/src/sage/algebras/fusion_rings/all.py @@ -17,3 +17,4 @@ lazy_import('sage.algebras.fusion_rings.fusion_ring', ['FusionRing']) del lazy_import + diff --git a/src/sage/algebras/fusion_rings/f_matrix.py b/src/sage/algebras/fusion_rings/f_matrix.py index 618bf40812a..47f04a5e125 100644 --- a/src/sage/algebras/fusion_rings/f_matrix.py +++ b/src/sage/algebras/fusion_rings/f_matrix.py @@ -1,5 +1,5 @@ r""" -F-Matrices of Fusion Rings +The F-Matrix of a Fusion Ring """ # **************************************************************************** # Copyright (C) 2019 Daniel Bump @@ -45,7 +45,7 @@ class FMatrix(SageObject): r""" - Return an F-Matrix factory for a :class:`FusionRing`. + An F-matrix for a :class:`FusionRing`. INPUT: @@ -222,9 +222,7 @@ class FMatrix(SageObject): smaller examples. For example the :class:`FusionRing` for `G_2` at level 2 is too large. When it is available, this method produces an F-matrix whose entries are in the same - cyclotomic field as the underlying :class:`FusionRing`. - - :: + cyclotomic field as the underlying :class:`FusionRing`. :: sage: f.find_cyclotomic_solution() Setting up hexagons and pentagons... @@ -254,9 +252,7 @@ class FMatrix(SageObject): modified, adding an attribute ``_basecoer`` that is a coercion from the cyclotomic field to the field containing the F-matrix. The field containing the F-matrix - is available through :meth:`field`. - - :: + is available through :meth:`field`. :: sage: f = FusionRing("B3", 2).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False, checkpoint=True) # not tested (~100 s) @@ -795,10 +791,8 @@ def get_coerce_map_from_fr_cyclotomic_field(self): When F-symbols are computed as elements of the associated :class:`FusionRing`'s base :class:`Cyclotomic field`, - we have ``self.field() == self.FR().field()`` and this method - returns the identity map on ``self.field()``. - - :: + we have ``self.field() == self.FR().field()`` and this + returns the identity map on ``self.field()``. :: sage: f = FusionRing("A2", 1).get_fmatrix() sage: f.find_orthogonal_solution(verbose=False) @@ -849,7 +843,7 @@ def get_fvars_in_alg_field(self): def get_radical_expression(self): """ - Return radical expression of F-symbols for easy visualization + Return a radical expression of F-symbols. EXAMPLES:: @@ -885,7 +879,8 @@ def _get_known_vals(self): def _get_known_nonz(self): r""" - Construct an ETuple indicating positions of known nonzero variables. + Construct an :class:`ETuple` indicating positions of + known nonzero variables. .. NOTE:: @@ -1930,7 +1925,7 @@ def _get_explicit_solution(self, eqns=None, verbose=True): one = self._field.one() for fx, rhs in self._ks.items(): if not self._solved[fx]: - lt = (ETuple({fx : 2}, n), one) + lt = (ETuple({fx: 2}, n), one) eqns.append(((lt, (ETuple({}, n), -rhs)))) eqns_partition = self._partition_eqns(verbose=verbose) @@ -1994,11 +1989,11 @@ def _get_explicit_solution(self, eqns=None, verbose=True): assert sum(self._solved) == nvars, "Some F-symbols are still missing...{}".format([self._poly_ring.gen(fx) for fx in range(nvars) if not self._solved[fx]]) # Backward substitution step. Traverse variables in reverse lexicographical order. (System is in triangular form) - self._fvars = {sextuple : apply_coeff_map(rhs, phi) for sextuple, rhs in self._fvars.items()} + self._fvars = {sextuple: apply_coeff_map(rhs, phi) for sextuple, rhs in self._fvars.items()} for fx, rhs in numeric_fvars.items(): self._fvars[self._idx_to_sextuple[fx]] = ((ETuple({}, nvars), rhs), ) _backward_subs(self, flatten=False) - self._fvars = {sextuple : constant_coeff(rhs, self._field) for sextuple, rhs in self._fvars.items()} + self._fvars = {sextuple: constant_coeff(rhs, self._field) for sextuple, rhs in self._fvars.items()} # Update base field attributes self._FR._field = self.field() @@ -2186,7 +2181,7 @@ def find_orthogonal_solution(self, checkpoint=False, save_results="", warm_start def _fix_gauge(self, algorithm=""): r""" - Fix the gauge by forcing F-symbols not already fixed to equal 1. + Fix the gauge by forcing F-symbols not already fixed to equal `1`. .. NOTE:: @@ -2221,7 +2216,7 @@ def _fix_gauge(self, algorithm=""): self._update_equations() def _substitute_degree_one(self, eqns=None): - """ + r""" Substitute known value from linear univariate polynomial and solve, following [Bond2007]_ p.37, for two-term linear equation for one of the variables. @@ -2287,7 +2282,7 @@ def _update_equations(self): def find_cyclotomic_solution(self, equations=None, algorithm="", verbose=True, output=False): r""" - Solve the the hexagon and pentagon relations to evaluate the F-matrix. + Solve the hexagon and pentagon relations to evaluate the F-matrix. This method (omitting the orthogonality constraints) produces output in the cyclotomic field, but it is very limited in the size @@ -2462,3 +2457,4 @@ def certify_pentagons(self, use_mp=True, verbose=False): print("Something went wrong. Pentagons remain.") self._fvars = fvars_copy return pe + diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx index bc1ad78e826..64b8254f13a 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx @@ -1,5 +1,5 @@ """ -Fast F-Matrix methods +Fast F-Matrix Methods """ # **************************************************************************** # Copyright (C) 2021 Guillermo Aboumrad @@ -539,3 +539,4 @@ cdef pent_verify(factory, tuple mp_params): feq_verif(factory, worker_results, fvars, Nk_ij, id_anyon, nonuple) if i % 50000000 == 0 and i and verbose: print("{:5d}m equations checked... {} potential misses so far...".format(i // 1000000, len(worker_results))) + diff --git a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx index c68bcd2e88c..9fcb4408c21 100644 --- a/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +++ b/src/sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx @@ -1,5 +1,5 @@ """ -Fast FusionRing methods for computing braid group representations +Fast Fusion Ring Methods for Computing Braid Group Representations """ # **************************************************************************** # Copyright (C) 2021 Guillermo Aboumrad diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index a4e27c3534a..a9bc2eed23a 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -572,8 +572,7 @@ def fvars_field(self): """ if self.is_multiplicity_free(): return self.get_fmatrix().field() - else: - raise ValueError("Method is only available for multiplicity free fusion rings.") + raise NotImplementedError("method is only available for multiplicity free fusion rings") def root_of_unity(self, r, base_coercion=True): r""" @@ -592,13 +591,12 @@ def root_of_unity(self, r, base_coercion=True): [1, -1, zeta24^4 - 1, zeta24^6, None, zeta24^4, None] """ n = 2 * r * self._cyclotomic_order - if n in ZZ: - ret = self.field().gen() ** n - if (not base_coercion) or (self._basecoer is None): - return ret - return self._basecoer(ret) - else: - return None + if n not in ZZ: + raise ValueError("not an integer root of unity") + ret = self.field().gen() ** n + if (not base_coercion) or (self._basecoer is None): + return ret + return self._basecoer(ret) def get_order(self): r""" @@ -908,11 +906,11 @@ def s_matrix(self, unitary=False, base_coercion=True): [0 0 0 1] """ b = self.basis() - S = matrix([[self.s_ij(b[x], b[y], base_coercion=base_coercion) for x in self.get_order()] for y in self.get_order()]) + S = matrix([[self.s_ij(b[x], b[y], base_coercion=base_coercion) + for x in self.get_order()] for y in self.get_order()]) if unitary: return S / self.total_q_order(base_coercion=base_coercion) - else: - return S + return S @cached_method def r_matrix(self, i, j, k, base_coercion=True): @@ -987,7 +985,7 @@ def global_q_dimension(self, base_coercion=True): sage: FusionRing("E6", 1).global_q_dimension() 3 """ - ret = sum(x.q_dimension(base_coercion=False)**2 for x in self.basis()) + ret = sum(x.q_dimension(base_coercion=False) ** 2 for x in self.basis()) if (not base_coercion) or (self._basecoer is None): return ret return self._basecoer(ret) @@ -1486,8 +1484,7 @@ def twist(self, reduced=True): f = twist.floor() twist -= f return twist + (f % 2) - else: - return twist + return twist def ribbon(self, base_coercion=True): r""" @@ -1565,3 +1562,4 @@ def q_dimension(self, base_coercion=True): if (not base_coercion) or (self.parent()._basecoer is None): return ret return self.parent()._basecoer(ret) + diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pxd b/src/sage/algebras/fusion_rings/poly_tup_engine.pxd index a4ddf9b92ae..22c6449385a 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pxd +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pxd @@ -21,3 +21,4 @@ cdef tuple reduce_poly_dict(dict eq_dict, ETuple nonz, KSHandler known_sq, Numbe cdef tuple _flatten_coeffs(tuple eq_tup) cpdef tuple _unflatten_coeffs(field, tuple eq_tup) cdef int has_appropriate_linear_term(tuple eq_tup) + diff --git a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx index 8255e463492..47e403c6d2c 100644 --- a/src/sage/algebras/fusion_rings/poly_tup_engine.pyx +++ b/src/sage/algebras/fusion_rings/poly_tup_engine.pyx @@ -350,7 +350,6 @@ cdef dict subs_squares(dict eq_dict, KSHandler known_sq): - ``eq_dict`` -- a dictionary of ``(ETuple, coeff)`` pairs representing a polynomial - - ``known_sq`` -- a dictionary of ``(int i, NumberFieldElement a)`` pairs such that `x_i^2 - a = 0` @@ -577,3 +576,4 @@ cpdef tuple poly_tup_sortkey(tuple eq_tup): key.append(-exp._data[2*i]) key.append(exp._data[2*i+1]) return tuple(key) + diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index b5c443d7cc2..5d7ec652613 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -1,11 +1,11 @@ r""" -Shared memory managers for F-symbol attributes. +Shared Memory Managers for F-Symbol Attributes This module provides an implementation for shared dictionary like state attributes required by the orthogonal F-matrix solver. -Currently, the attributes only work when the base field of the FMatrix -factory is a cyclotomic field. +Currently, the attributes only work when the base field of the +:class:`FMatrix` factory is a cyclotomic field. """ # **************************************************************************** @@ -773,3 +773,4 @@ def make_FvarsHandler(n, field, idx_map, init_data): sage: f.shutdown_worker_pool() """ return FvarsHandler(n, field, idx_map, init_data=init_data) + From d9de90ace8082a5ac80421e2f57c7898bc4dd991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 30 Oct 2022 13:48:17 +0100 Subject: [PATCH 275/414] fix deprecated use of PyEval_Call --- src/sage/modular/arithgroup/farey.cpp | 2 +- src/sage/symbolic/ginac/function.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/modular/arithgroup/farey.cpp b/src/sage/modular/arithgroup/farey.cpp index a98ade699d0..b999728b57e 100644 --- a/src/sage/modular/arithgroup/farey.cpp +++ b/src/sage/modular/arithgroup/farey.cpp @@ -256,7 +256,7 @@ bool is_element_general::is_member(const SL2Z& m) const { PyObject* arg = convert_to_SL2Z(m); PyObject* tuple = PyTuple_New(1); PyTuple_SetItem(tuple, 0, arg); - PyObject *result = PyEval_CallObject(method, tuple); + PyObject *result = PyObject_Call(method, tuple); Py_DECREF(tuple); if( not PyBool_Check(result) ) { cerr << "__contains__ does not return bool." << endl; diff --git a/src/sage/symbolic/ginac/function.cpp b/src/sage/symbolic/ginac/function.cpp index 689e2b8a22f..67d608c6240 100644 --- a/src/sage/symbolic/ginac/function.cpp +++ b/src/sage/symbolic/ginac/function.cpp @@ -976,7 +976,7 @@ ex function::evalf(int level, PyObject* kwds) const // convert seq to a PyTuple of Expressions PyObject* args = py_funcs.exvector_to_PyTuple(eseq); // call opt.evalf_f with this list - PyObject* pyresult = PyEval_CallObjectWithKeywords( + PyObject* pyresult = PyObject_Call( PyObject_GetAttrString(reinterpret_cast(opt.evalf_f), "_evalf_"), args, kwds); Py_DECREF(args); @@ -1056,7 +1056,7 @@ ex function::series(const relational & r, int order, unsigned options) const // add the point of expansion as a keyword argument PyDict_SetItemString(kwds, "at", py_funcs.ex_to_pyExpression(r.rhs())); // call opt.series_f with this list - PyObject* pyresult = PyEval_CallObjectWithKeywords( + PyObject* pyresult = PyObject_Call( PyObject_GetAttrString(reinterpret_cast(opt.series_f), "_series_"), args, kwds); Py_DECREF(args); @@ -1321,7 +1321,7 @@ ex function::derivative(const symbol & s) const PyObject* kwds = Py_BuildValue("{s:O}","diff_param", symb); // call opt.derivative_f with this list - PyObject* pyresult = PyEval_CallObjectWithKeywords( + PyObject* pyresult = PyObject_Call( PyObject_GetAttrString( reinterpret_cast(opt.derivative_f), "_tderivative_"), args, kwds); @@ -1478,7 +1478,7 @@ ex function::pderivative(unsigned diff_param) const // partial differentiation // create a dictionary {'diff_param': diff_param} PyObject* kwds = Py_BuildValue("{s:I}","diff_param",diff_param); // call opt.derivative_f with this list - PyObject* pyresult = PyEval_CallObjectWithKeywords( + PyObject* pyresult = PyObject_Call( PyObject_GetAttrString(reinterpret_cast(opt.derivative_f), "_derivative_"), args, kwds); Py_DECREF(args); @@ -1557,7 +1557,7 @@ ex function::power(const ex & power_param) const // power of function PyObject* kwds = PyDict_New(); PyDict_SetItemString(kwds, "power_param", py_funcs.ex_to_pyExpression(power_param)); // call opt.power_f with this list - PyObject* pyresult = PyEval_CallObjectWithKeywords( + PyObject* pyresult = PyObject_Call( PyObject_GetAttrString(reinterpret_cast(opt.power_f), "_power_"), args, kwds); Py_DECREF(args); From 285c58f75903c835647cf1ecbaa5cc220890368d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 30 Oct 2022 14:28:02 +0100 Subject: [PATCH 276/414] fix detail --- src/sage/modular/arithgroup/farey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/modular/arithgroup/farey.cpp b/src/sage/modular/arithgroup/farey.cpp index b999728b57e..34f5e5727db 100644 --- a/src/sage/modular/arithgroup/farey.cpp +++ b/src/sage/modular/arithgroup/farey.cpp @@ -256,7 +256,7 @@ bool is_element_general::is_member(const SL2Z& m) const { PyObject* arg = convert_to_SL2Z(m); PyObject* tuple = PyTuple_New(1); PyTuple_SetItem(tuple, 0, arg); - PyObject *result = PyObject_Call(method, tuple); + PyObject *result = PyObject_CallObject(method, tuple); Py_DECREF(tuple); if( not PyBool_Check(result) ) { cerr << "__contains__ does not return bool." << endl; From 88733f9402fa27c280430dc1009610d263b67ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 30 Oct 2022 17:37:24 +0100 Subject: [PATCH 277/414] remove traces of lgtm --- .lgtm.yml | 23 ---------------------- src/doc/en/developer/tools.rst | 11 ----------- src/sage/algebras/lie_algebras/examples.py | 17 ++++++++++------ 3 files changed, 11 insertions(+), 40 deletions(-) delete mode 100644 .lgtm.yml diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index 0650ffa1eee..00000000000 --- a/.lgtm.yml +++ /dev/null @@ -1,23 +0,0 @@ -queries: - - exclude: py/call/wrong-named-class-argument - - exclude: py/call/wrong-number-class-arguments - - exclude: py/similar-function - - exclude: py/unsafe-cyclic-import -path_classifiers: - imports_only: - - "**/all.py" - - "**/all_*.py" - - "**/*catalog*.py" - - "**/species/library.py" - - "**/categories/basic.py" - - "**/combinat/ribbon.py" - - "**/combinat/family.py" - - "**/interacts/geometry.py" - - "**/matroids/advanced.py" - - "**/matroids/named_matroids.py" - - "**/modular/congroup.py" - - "**/quadratic_forms/quadratic_form__mass.py" -extraction: - python: - python_setup: - version: 3 diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index becaad7f829..e2ac3ef82dd 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -319,14 +319,3 @@ Pyright Pyflakes ======== `Pyflakes `_ checks for common coding errors. - - -.. _section-tools-lgtm: - -LGTM -==== -The website ``lgtm.com`` offers a detailed diagnostic about the global code quality and its evolution. - -The reports can be found `here `_. - -Our choice of configuration is made in ``.lgtm.yml``. diff --git a/src/sage/algebras/lie_algebras/examples.py b/src/sage/algebras/lie_algebras/examples.py index d466856a7b9..99106f8a1ec 100644 --- a/src/sage/algebras/lie_algebras/examples.py +++ b/src/sage/algebras/lie_algebras/examples.py @@ -41,7 +41,7 @@ from sage.algebras.lie_algebras.classical_lie_algebra import ClassicalMatrixLieAlgebra as ClassicalMatrix -# the next 6 lines are here to silent pyflakes and lgtm warnings +# the next 6 lines are here to silent pyflakes warnings assert VirasoroAlgebra assert RankTwoHeisenbergVirasoro assert OnsagerAlgebra @@ -97,6 +97,7 @@ def three_dimensional(R, a, b, c, d, names=['X', 'Y', 'Z']): s_coeff = {(X,Y): {Z:a, Y:d}, (Y,Z): {X:b}, (Z,X): {Y:c, Z:d}} return LieAlgebraWithStructureCoefficients(R, s_coeff, tuple(names)) + def cross_product(R, names=['X', 'Y', 'Z']): r""" The Lie algebra of `\RR^3` defined by the usual cross product @@ -160,7 +161,7 @@ def three_dimensional_by_rank(R, n, a=None, names=['X', 'Y', 'Z']): return AbelianLieAlgebra(R, names=names) if n == 1: - L = three_dimensional(R, 0, 1, 0, 0, names=names) # Strictly upper triangular matrices + L = three_dimensional(R, 0, 1, 0, 0, names=names) # Strictly upper triangular matrices L.rename("Lie algebra of 3x3 strictly upper triangular matrices over {}".format(R)) return L @@ -184,18 +185,19 @@ def three_dimensional_by_rank(R, n, a=None, names=['X', 'Y', 'Z']): return L if n == 3: - #return sl(R, 2) + # return sl(R, 2) from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients E = names[0] F = names[1] H = names[2] - s_coeff = { (E,F): {H:R.one()}, (H,E): {E:R(2)}, (H,F): {F:R(-2)} } + s_coeff = {(E, F): {H: R.one()}, (H, E): {E: R(2)}, (H, F): {F: R(-2)}} L = LieAlgebraWithStructureCoefficients(R, s_coeff, tuple(names)) L.rename("sl2 over {}".format(R)) return L raise ValueError("Invalid rank") + def affine_transformations_line(R, names=['X', 'Y'], representation='bracket'): """ The Lie algebra of affine transformations of the line. @@ -233,6 +235,7 @@ def affine_transformations_line(R, names=['X', 'Y'], representation='bracket'): L.rename("Lie algebra of affine transformations of a line over {}".format(R)) return L + def abelian(R, names=None, index_set=None): """ Return the abelian Lie algebra generated by ``names``. @@ -365,6 +368,7 @@ def upper_triangular_matrices(R, n): L.rename("Lie algebra of {}-dimensional upper triangular matrices over {}".format(n, L.base_ring())) return L + def strictly_upper_triangular_matrices(R, n): r""" Return the Lie algebra `\mathfrak{n}_k` of strictly `k \times k` upper @@ -401,13 +405,13 @@ def strictly_upper_triangular_matrices(R, n): MS = MatrixSpace(R, n, sparse=True) one = R.one() names = tuple('n{}'.format(i) for i in range(n-1)) - gens = tuple(MS({(i,i+1):one}) for i in range(n-1)) + gens = tuple(MS({(i,i+1): one}) for i in range(n-1)) L = LieAlgebraFromAssociative(MS, gens, names=names) L.rename("Lie algebra of {}-dimensional strictly upper triangular matrices over {}".format(n, L.base_ring())) return L ##################################################################### -## Classical Lie algebras +# Classical Lie algebras def sl(R, n, representation='bracket'): @@ -511,6 +515,7 @@ def su(R, n, representation='matrix'): return MatrixCompactRealForm(R, CartanType(['A', n-1])) raise ValueError("invalid representation") + def so(R, n, representation='bracket'): r""" The Lie algebra `\mathfrak{so}_n`. From 52d539b1e0a8b9d13797168a8cf0d1e4fafef8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 30 Oct 2022 18:34:13 +0100 Subject: [PATCH 278/414] fix typo --- src/sage/algebras/lie_algebras/examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/lie_algebras/examples.py b/src/sage/algebras/lie_algebras/examples.py index 99106f8a1ec..2017ad3c6d2 100644 --- a/src/sage/algebras/lie_algebras/examples.py +++ b/src/sage/algebras/lie_algebras/examples.py @@ -41,7 +41,7 @@ from sage.algebras.lie_algebras.classical_lie_algebra import ClassicalMatrixLieAlgebra as ClassicalMatrix -# the next 6 lines are here to silent pyflakes warnings +# the next 6 lines are here to silence pyflakes warnings assert VirasoroAlgebra assert RankTwoHeisenbergVirasoro assert OnsagerAlgebra From ae432e3ffc5e2464021f4e0d96e6d060d57468ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 30 Oct 2022 20:55:10 +0100 Subject: [PATCH 279/414] clean W293 in py files (but one) and some pyx files --- src/sage/calculus/var.pyx | 4 ++-- src/sage/databases/knotinfo_db.py | 8 +++---- src/sage/doctest/fixtures.py | 1 - src/sage/features/__init__.py | 3 +-- src/sage/geometry/fan_morphism.py | 10 ++++---- .../geometry/hyperplane_arrangement/plot.py | 12 +++++----- .../triangulation/point_configuration.py | 4 ++-- src/sage/interfaces/gap.py | 2 -- src/sage/interfaces/gap3.py | 12 ++++++---- src/sage/interfaces/interface.py | 2 -- src/sage/interfaces/lisp.py | 4 +--- src/sage/interfaces/macaulay2.py | 2 -- src/sage/interfaces/magma.py | 6 +---- src/sage/interfaces/mathematica.py | 4 +--- src/sage/interfaces/mathics.py | 2 -- src/sage/interfaces/maxima_abstract.py | 2 -- src/sage/interfaces/octave.py | 2 -- src/sage/interfaces/primecount.py | 2 +- src/sage/interfaces/r.py | 4 +--- src/sage/libs/pari/convert_gmp.pyx | 2 +- src/sage/manifolds/catalog.py | 4 ++-- src/sage/matrix/matrix_gap.pyx | 2 +- src/sage/misc/fast_methods.pyx | 2 +- src/sage/misc/unknown.py | 2 -- .../modular/btquotients/pautomorphicform.py | 2 -- .../numerical/backends/generic_backend.pyx | 4 ++-- src/sage/numerical/gauss_legendre.pyx | 4 ++-- src/sage/numerical/linear_tensor.py | 10 ++++---- .../numerical/linear_tensor_constraints.py | 8 +++---- src/sage/rings/function_field/element.pyx | 4 ++-- src/sage/rings/noncommutative_ideals.pyx | 2 +- .../number_field/number_field_morphisms.pyx | 4 ++-- .../small_primes_of_degree_one.py | 2 +- .../polynomial/laurent_polynomial_ideal.py | 24 +++++++++---------- .../polynomial/laurent_polynomial_ring.py | 2 +- .../rings/semirings/tropical_semiring.pyx | 2 +- src/sage/rings/tate_algebra_ideal.pyx | 6 ++--- src/sage/sat/solvers/cryptominisat.py | 9 +++---- src/sage/sat/solvers/picosat.py | 9 +++---- src/sage/sat/solvers/satsolver.pyx | 2 +- src/sage/schemes/elliptic_curves/padics.py | 6 ++--- .../product_projective/rational_point.py | 2 +- .../projective/projective_rational_point.py | 2 +- .../judson-abstract-algebra/boolean-sage.py | 2 -- .../judson-abstract-algebra/rings-sage.py | 1 - 45 files changed, 89 insertions(+), 116 deletions(-) diff --git a/src/sage/calculus/var.pyx b/src/sage/calculus/var.pyx index 85359ac098d..ed7a4305188 100644 --- a/src/sage/calculus/var.pyx +++ b/src/sage/calculus/var.pyx @@ -343,11 +343,11 @@ def function(s, **kwds): TESTS: Make sure that :trac:`15860` is fixed and whitespaces are removed:: - + sage: function('A, B') (A, B) sage: B - B + B """ G = globals() # this is the reason the code must be in Cython. v = new_function(s, **kwds) diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index 759ded16cac..d70e6be6bac 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -603,16 +603,16 @@ def _create_data_sobj(self, sobj_path=None): val_list.append(knot_list[i][col.name]) - if col.column_type() != col.types.OnlyKnots: - for i in range(1 , len_links): + if col.column_type() != col.types.OnlyKnots: + for i in range(1, len_links): if col.name == self._names_column: link_name = link_list[i][col.name] link_name = link_name.replace('{', '_') link_name = link_name.replace(',', '_') link_name = link_name.replace('}', '') - + num_comp = int(link_list[i][self._components_column]) - row_dict[link_name] = [i + len_knots - 2 , num_comp] + row_dict[link_name] = [i + len_knots - 2, num_comp] val_list.append(link_list[i][col.name]) diff --git a/src/sage/doctest/fixtures.py b/src/sage/doctest/fixtures.py index 24d0bf9ae01..e6f8fc45f8e 100644 --- a/src/sage/doctest/fixtures.py +++ b/src/sage/doctest/fixtures.py @@ -340,7 +340,6 @@ def trace_method(obj, meth, **kwds): - ``reads`` -- (default: ``True``) whether to trace read access as well. - EXAMPLES:: diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index fd899aa4770..ed1059cecbe 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -371,8 +371,6 @@ def __bool__(self): """ return bool(self.is_present) - - def __repr__(self): r""" TESTS:: @@ -386,6 +384,7 @@ def __repr__(self): _cache_package_systems = None + def package_systems(): """ Return a list of :class:`~sage.features.pkg_systems.PackageSystem` objects diff --git a/src/sage/geometry/fan_morphism.py b/src/sage/geometry/fan_morphism.py index d7ba7ac7ea6..e94e2cce8c0 100644 --- a/src/sage/geometry/fan_morphism.py +++ b/src/sage/geometry/fan_morphism.py @@ -1477,7 +1477,7 @@ def is_dominant(self): If the fan morphism is dominant, then the associated morphism of toric varieties is dominant in the algebraic-geometric sense (that is, surjective onto a dense subset). - + OUTPUT: Boolean. @@ -1878,13 +1878,13 @@ def factor(self): def relative_star_generators(self, domain_cone): """ Return the relative star generators of ``domain_cone``. - + INPUT: - + - ``domain_cone`` -- a cone of the :meth:`domain_fan` of ``self``. - + OUTPUT: - + - :meth:`~RationalPolyhedralFan.star_generators` of ``domain_cone`` viewed as a cone of :meth:`preimage_fan` of :meth:`image_cone` of ``domain_cone``. diff --git a/src/sage/geometry/hyperplane_arrangement/plot.py b/src/sage/geometry/hyperplane_arrangement/plot.py index 875fa0de46e..9f89585578e 100644 --- a/src/sage/geometry/hyperplane_arrangement/plot.py +++ b/src/sage/geometry/hyperplane_arrangement/plot.py @@ -139,7 +139,7 @@ def plot(hyperplane_arrangement, **kwds): :mod:`sage.geometry.hyperplane_arrangement.plot`. OUTPUT: - + A graphics object of the plot. EXAMPLES:: @@ -338,7 +338,6 @@ def plot_hyperplane(hyperplane, **kwds): sage: a.plot(point_size=100,hyperplane_label='hello') # optional - sage.plot Graphics object consisting of 3 graphics primitives - sage: H2. = HyperplaneArrangements(QQ) sage: b = 3*x + 4*y + 5 sage: b.plot() # optional - sage.plot @@ -469,14 +468,15 @@ def plot_hyperplane(hyperplane, **kwds): def legend_3d(hyperplane_arrangement, hyperplane_colors, length): r""" - Create plot of a 3d legend for an arrangement of planes in 3-space. The - ``length`` parameter determines whether short or long labels are used in - the legend. + Create plot of a 3d legend for an arrangement of planes in 3-space. + + The ``length`` parameter determines whether short or long labels + are used in the legend. INPUT: - ``hyperplane_arrangement`` -- a hyperplane arrangement - + - ``hyperplane_colors`` -- list of colors - ``length`` -- either ``'short'`` or ``'long'`` diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index 4e86b8fc3e3..1bf933700c9 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -1832,7 +1832,7 @@ def contained_simplex(self, large=True, initial_point=None, point_order=None): :class:`~sage.geometry.triangulation.base.Point` or ``None`` (default). A specific point to start with when picking the simplex vertices. - + - ``point_order`` -- a list or tuple of (some or all) :class:`~sage.geometry.triangulation.base.Point` s or ``None`` (default). @@ -1871,7 +1871,7 @@ def contained_simplex(self, large=True, initial_point=None, point_order=None): sage: pc.contained_simplex(point_order = [pc[0],pc[3],pc[4]]) (P(0, 0), P(1, 1)) - + TESTS:: sage: pc = PointConfiguration([[0,0],[0,1],[1,0]]) diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index ba175d4e340..c80a847d7e9 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -952,8 +952,6 @@ def __bool__(self): P = self._check_valid() return self != P(0) and repr(self) != 'false' - - def __len__(self): """ EXAMPLES:: diff --git a/src/sage/interfaces/gap3.py b/src/sage/interfaces/gap3.py index fc21ee46d06..78d05035cbf 100644 --- a/src/sage/interfaces/gap3.py +++ b/src/sage/interfaces/gap3.py @@ -12,7 +12,7 @@ The experimental package for GAP3 is Jean Michel's pre-packaged GAP3, which is a minimal GAP3 distribution containing packages that have - no equivalent in GAP4, see :trac:`20107` and also + no equivalent in GAP4, see :trac:`20107` and also https://webusers.imj-prg.fr/~jean.michel/gap3/ @@ -212,7 +212,7 @@ ... """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2010 Franco Saliola # # Distributed under the terms of the GNU General Public License (GPL) @@ -224,8 +224,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.misc.cachefunc import cached_method from sage.interfaces.expect import Expect @@ -236,6 +236,7 @@ # gap3_cmd should point to the gap3 executable gap3_cmd = 'gap3' + class Gap3(Gap_generic): r""" A simple Expect interface to GAP3. @@ -624,9 +625,10 @@ def _tab_completion(self): """ return [] - + gap3 = Gap3() + class GAP3Element(GapElement_generic): r""" A GAP3 element diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 7e612f049f2..90d4ba6d1d0 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -1340,8 +1340,6 @@ def __bool__(self): P._false_symbol()) return P.eval(cmd) != P._true_symbol() - - def __float__(self): """ EXAMPLES:: diff --git a/src/sage/interfaces/lisp.py b/src/sage/interfaces/lisp.py index 0f9d09605bf..7b057a4de3c 100644 --- a/src/sage/interfaces/lisp.py +++ b/src/sage/interfaces/lisp.py @@ -434,8 +434,6 @@ def __bool__(self): """ return self != 0 and repr(self) != 'NIL' - - def _add_(self, right): """ EXAMPLES:: @@ -445,7 +443,7 @@ def _add_(self, right): 3 """ P = self._check_valid() - return P.new('(+ %s %s)'%(self._name, right._name)) + return P.new('(+ %s %s)' % (self._name, right._name)) def _sub_(self, right): """ diff --git a/src/sage/interfaces/macaulay2.py b/src/sage/interfaces/macaulay2.py index 14d217cff9e..b5bcac4003f 100644 --- a/src/sage/interfaces/macaulay2.py +++ b/src/sage/interfaces/macaulay2.py @@ -1179,8 +1179,6 @@ def __bool__(self): P = self.parent() return P.eval('{0}===false or {0}==0'.format(self._name)) != 'true' - - def sage_polystring(self): """ If this Macaulay2 element is a polynomial, return a string diff --git a/src/sage/interfaces/magma.py b/src/sage/interfaces/magma.py index e34e5a13aa3..89dc1aa8140 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -2652,17 +2652,13 @@ def __bool__(self): pass return True - - def sub(self, gens): """ Return the sub-object of self with given gens. INPUT: - - - ``gens`` - object or list/tuple of generators - + - ``gens`` -- object or list/tuple of generators EXAMPLES:: diff --git a/src/sage/interfaces/mathematica.py b/src/sage/interfaces/mathematica.py index 6d0558d8717..9c6e3ca86e9 100644 --- a/src/sage/interfaces/mathematica.py +++ b/src/sage/interfaces/mathematica.py @@ -377,7 +377,7 @@ sage: FL=[sin, cos, tan, csc, sec, cot, # optional - mathematica ....: sinh, cosh, tanh, csch, sech, coth] sage: IFL=[arcsin, arccos, arctan, arccsc, # optional - mathematica - ....: arcsec, arccot, arcsinh, arccosh, + ....: arcsec, arccot, arcsinh, arccosh, ....: arctanh, arccsch, arcsech, arccoth] sage: [mathematica.TrigToExp(u(x)).sage() # optional - mathematica ....: for u in FL] @@ -1079,8 +1079,6 @@ def __bool__(self): cmd = '%s===%s' % (self._name, P._false_symbol()) return P.eval(cmd).strip() != P._true_symbol() - - def n(self, *args, **kwargs): r""" Numerical approximation by converting to Sage object first diff --git a/src/sage/interfaces/mathics.py b/src/sage/interfaces/mathics.py index aa86c956635..0bf0bc78829 100644 --- a/src/sage/interfaces/mathics.py +++ b/src/sage/interfaces/mathics.py @@ -1242,8 +1242,6 @@ def __bool__(self): cmd = '%s===%s' % (self._name, P._false_symbol()) return not str(P(cmd)) == P._true_symbol() - - def n(self, *args, **kwargs): r""" Numerical approximation by converting to Sage object first diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index 3fb1a53a893..cc1d88160bb 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -1148,8 +1148,6 @@ def __bool__(self): # but be careful, since for relations things like is(equal(a,b)) are # what Maxima needs - - def _richcmp_(self, other, op): """ Compare this Maxima object with ``other``. diff --git a/src/sage/interfaces/octave.py b/src/sage/interfaces/octave.py index b22c2fb1d3a..1f3c2cbd3b2 100644 --- a/src/sage/interfaces/octave.py +++ b/src/sage/interfaces/octave.py @@ -665,8 +665,6 @@ def __bool__(self): """ return str(self) != ' [](0x0)' and any(x != '0' for x in str(self).split()) - - def _matrix_(self, R=None): r""" Return Sage matrix from this octave element. diff --git a/src/sage/interfaces/primecount.py b/src/sage/interfaces/primecount.py index e647bf9b980..518378e1fe2 100644 --- a/src/sage/interfaces/primecount.py +++ b/src/sage/interfaces/primecount.py @@ -1,5 +1,5 @@ from sage.misc.superseded import deprecation deprecation(32894, "the module sage.interfaces.primecount is deprecated - use primecountpy.primecount instead") from sage.misc.lazy_import import lazy_import -lazy_import("primecountpy.primecount", ['phi', 'nth_prime', 'prime_pi', 'prime_pi_128'], +lazy_import("primecountpy.primecount", ['phi', 'nth_prime', 'prime_pi', 'prime_pi_128'], deprecation=(32894, "the module sage.interfaces.primecount is deprecated - use primecountpy.primecount instead")) diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 72481d3e1ef..19b8a7df600 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -1606,9 +1606,7 @@ def __bool__(self): sage: bool(r(1)) # optional - rpy2 True """ - return "FALSE" in repr(self==0) - - + return "FALSE" in repr(self == 0) def _comparison(self, other, symbol): """ diff --git a/src/sage/libs/pari/convert_gmp.pyx b/src/sage/libs/pari/convert_gmp.pyx index fe23a00cdc7..a39caa379b4 100644 --- a/src/sage/libs/pari/convert_gmp.pyx +++ b/src/sage/libs/pari/convert_gmp.pyx @@ -159,7 +159,7 @@ cdef Gen rational_matrix(mpq_t** B, long nr, long nc): """ Create a new PARI matrix of type ``t_MAT`` from a given array of GMP rationals ``mpq_t``. - + INPUT: - ``B`` -- a 2-dimensional array of ``mpq_t`` values. This array is diff --git a/src/sage/manifolds/catalog.py b/src/sage/manifolds/catalog.py index 7dcdad7b113..e71faca3072 100644 --- a/src/sage/manifolds/catalog.py +++ b/src/sage/manifolds/catalog.py @@ -364,9 +364,9 @@ def RealProjectiveSpace(dim=2): charts = {0: U0.chart(''.join(names[1:]))} # create the charts - for j in range(1, dim+1): + for j in range(1, dim + 1): U = P.open_subset(name=f'U{j}', latex_name=f'U_{j}') - + # The chart where we assert that x_i == 1 Cj = U.chart(''.join(names[:j] + names[j+1:])) gj = Cj[:] diff --git a/src/sage/matrix/matrix_gap.pyx b/src/sage/matrix/matrix_gap.pyx index 0102d045e6c..8485760e9cb 100644 --- a/src/sage/matrix/matrix_gap.pyx +++ b/src/sage/matrix/matrix_gap.pyx @@ -327,7 +327,7 @@ cdef class Matrix_gap(Matrix_dense): def transpose(self): r""" Return the transpose of this matrix. - + EXAMPLES:: sage: M = MatrixSpace(QQ, 2, implementation='gap') diff --git a/src/sage/misc/fast_methods.pyx b/src/sage/misc/fast_methods.pyx index eb73cb6bc3e..800bd798fb6 100644 --- a/src/sage/misc/fast_methods.pyx +++ b/src/sage/misc/fast_methods.pyx @@ -343,7 +343,7 @@ class Singleton(WithEqualityById, metaclass=ClasscallMetaclass): sage: __main__.C = C # ... in doctests sage: loads(dumps(c)) is copy(c) is C() # indirect doctest True - + The pickle data mainly consist of the class of the unique instance, which may be a subclass of the original class used to create the instance.If the class is replaced by a sub-sub-class after creation diff --git a/src/sage/misc/unknown.py b/src/sage/misc/unknown.py index 39f7fed8f41..b87a3e67506 100644 --- a/src/sage/misc/unknown.py +++ b/src/sage/misc/unknown.py @@ -143,8 +143,6 @@ def __bool__(self): """ raise UnknownError('Unknown does not evaluate in boolean context') - - def __and__(self, other): """ The ``&`` logical operation. diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index 890cdc3cd4c..6ba6bb78bbe 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -1667,8 +1667,6 @@ def __bool__(self): """ return any(not o.is_zero() for o in self._value) - - def __getitem__(self, e1): r""" Evaluate a `p`-adic automorphic form on a matrix in `GL_2(\QQ_p)`. diff --git a/src/sage/numerical/backends/generic_backend.pyx b/src/sage/numerical/backends/generic_backend.pyx index ac167aefa8d..f07a4d05f78 100644 --- a/src/sage/numerical/backends/generic_backend.pyx +++ b/src/sage/numerical/backends/generic_backend.pyx @@ -878,7 +878,7 @@ cdef class GenericBackend: tester = self._tester(**options) p = self tester.assertGreaterEqual(self.ncols(), 0) - + cpdef int nrows(self): """ Return the number of rows/constraints. @@ -1265,7 +1265,7 @@ cdef class GenericBackend: "{}({}) does not match".format(method, i)) for method in ("row_bounds", "row", "row_name"): assert_equal_row_data(method) - + def _test_copy(self, **options): """ Test whether the backend can be copied diff --git a/src/sage/numerical/gauss_legendre.pyx b/src/sage/numerical/gauss_legendre.pyx index 175513687df..0b8dd4361e3 100644 --- a/src/sage/numerical/gauss_legendre.pyx +++ b/src/sage/numerical/gauss_legendre.pyx @@ -216,7 +216,7 @@ def nodes(degree, prec): def estimate_error(results, prec, epsilon): r""" Routine to estimate the error in a list of quadrature approximations. - + The method used is based on Borwein, Bailey, and Girgensohn. As mentioned in mpmath: Although not very conservative, this method seems to be very robust in practice. @@ -226,7 +226,7 @@ def estimate_error(results, prec, epsilon): of the maximum norm of the error in the last approximation. INPUT: - + - ``results`` -- list. List of approximations to estimate the error from. Should be at least length 2. - ``prec`` -- integer. Binary precision at which computations are happening. diff --git a/src/sage/numerical/linear_tensor.py b/src/sage/numerical/linear_tensor.py index a210ff52fd7..26d94b12cc6 100644 --- a/src/sage/numerical/linear_tensor.py +++ b/src/sage/numerical/linear_tensor.py @@ -150,7 +150,7 @@ def LinearTensorParent(free_module_parent, linear_functions_parent): for a given base ring. INPUT: - + - ``free_module_parent`` -- module. A free module, like vector or matrix space. @@ -267,7 +267,7 @@ def is_vector_space(self): sage: LF = mip.linear_functions_parent() sage: LF.tensor(RDF^2).is_vector_space() True - sage: LF.tensor(RDF^(2,2)).is_vector_space() + sage: LF.tensor(RDF^(2,2)).is_vector_space() False """ from sage.modules.free_module import is_FreeModule @@ -319,7 +319,7 @@ def linear_functions(self): def _repr_(self): """ Return a string representation - + OUTPUT: String. @@ -364,7 +364,7 @@ def _convert_constant(self, m): return m_vector else: return M(m) - + def _element_constructor_(self, x): """ Construct a :class:`LinearTensor` from ``x``. @@ -393,7 +393,7 @@ def _element_constructor_(self, x): sage: LT(123) # indirect doctest (123.0, 123.0) - + Similar, over ``QQ`` and with matrices instead of vectors:: sage: p_QQ = MixedIntegerLinearProgram(solver='ppl') diff --git a/src/sage/numerical/linear_tensor_constraints.py b/src/sage/numerical/linear_tensor_constraints.py index 3da0cd4719a..6535867f8c8 100644 --- a/src/sage/numerical/linear_tensor_constraints.py +++ b/src/sage/numerical/linear_tensor_constraints.py @@ -317,8 +317,8 @@ class LinearTensorConstraintsParent_class(Parent): Field and Linear functions over Real Double Field sage: from sage.numerical.linear_tensor_constraints import LinearTensorConstraintsParent sage: LTC = LinearTensorConstraintsParent(LT); LTC - Linear constraints in the tensor product of Vector space of - dimension 2 over Real Double Field and Linear functions over + Linear constraints in the tensor product of Vector space of + dimension 2 over Real Double Field and Linear functions over Real Double Field sage: type(LTC) @@ -330,10 +330,10 @@ def __init__(self, linear_tensor_parent): The Python constructor INPUT: - + - ``linear_tensor_parent`` -- instance of :class:`LinearTensorParent_class`. - + TESTS:: sage: from sage.numerical.linear_functions import LinearFunctionsParent diff --git a/src/sage/rings/function_field/element.pyx b/src/sage/rings/function_field/element.pyx index 41a78f11846..233e8c55d78 100644 --- a/src/sage/rings/function_field/element.pyx +++ b/src/sage/rings/function_field/element.pyx @@ -281,13 +281,13 @@ cdef class FunctionFieldElement(FieldElement): Return the max degree between the denominator and numerator. EXAMPLES:: - + sage: FF. = FunctionField(QQ) sage: f = (t^2 + 3) / (t^3 - 1/3); f (t^2 + 3)/(t^3 - 1/3) sage: f.degree() 3 - + sage: FF. = FunctionField(QQ) sage: f = (t+8); f t + 8 diff --git a/src/sage/rings/noncommutative_ideals.pyx b/src/sage/rings/noncommutative_ideals.pyx index 29599c2ac0f..ea3d3c43f56 100644 --- a/src/sage/rings/noncommutative_ideals.pyx +++ b/src/sage/rings/noncommutative_ideals.pyx @@ -402,7 +402,7 @@ class Ideal_nc(Ideal_generic): gens = [z for z in (x * y for x in self.gens() for y in other.gens()) if z] return self.ring().ideal(gens, side='twosided') raise NotImplementedError("cannot multiply non-commutative ideals") - + if not isinstance(other, Ideal_nc): # Perhaps other is a ring and thus has its own # multiplication. diff --git a/src/sage/rings/number_field/number_field_morphisms.pyx b/src/sage/rings/number_field/number_field_morphisms.pyx index 9f77c9e3949..104603f4a73 100644 --- a/src/sage/rings/number_field/number_field_morphisms.pyx +++ b/src/sage/rings/number_field/number_field_morphisms.pyx @@ -695,7 +695,7 @@ cdef class CyclotomicFieldEmbedding(NumberFieldEmbedding): def section(self): """ Return the section of ``self``. - + EXAMPLES:: sage: from sage.rings.number_field.number_field_morphisms import CyclotomicFieldEmbedding @@ -740,7 +740,7 @@ cdef class CyclotomicFieldConversion(Map): def __init__(self, K, L): """ Construct a conversion map between cyclotomic fields. - + EXAMPLES:: sage: from sage.rings.number_field.number_field_morphisms import CyclotomicFieldEmbedding diff --git a/src/sage/rings/number_field/small_primes_of_degree_one.py b/src/sage/rings/number_field/small_primes_of_degree_one.py index 93cb7d97f70..facd5207a1c 100644 --- a/src/sage/rings/number_field/small_primes_of_degree_one.py +++ b/src/sage/rings/number_field/small_primes_of_degree_one.py @@ -227,7 +227,7 @@ def __next__(self): [29, 41, 61, 89, 101, 109, 149, 181, 229, 241] sage: ids[9] == N.ideal(3*a + 1/2*b + 5/2) True - + We test that :trac:`23468` is fixed:: sage: R. = QQ[] diff --git a/src/sage/rings/polynomial/laurent_polynomial_ideal.py b/src/sage/rings/polynomial/laurent_polynomial_ideal.py index f205371d822..76d1b495274 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ideal.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ideal.py @@ -2,7 +2,7 @@ r""" Ideals in Laurent polynomial rings. -For `R` a commutative ring, ideals in the Laurent polynomial ring +For `R` a commutative ring, ideals in the Laurent polynomial ring `R[x_1^{\pm 1}, x_2^{\pm 1}, \ldots, x_n^{\pm 1}]` are implemented as ideals in the ordinary polynomial ring `R[x_1, \ldots, x_n]` which are saturated with respect to the ideal `(x_1 \cdots x_n)`. @@ -35,7 +35,7 @@ def __init__(self, ring, gens, coerce=True, hint=None): associated ordinary polynomial ring `R[x_1,\ldots,x_n]` which is saturated with respect to the ideal `(x_1 \cdots x_n)`. Since computing the saturation can be expensive, we employ some strategies to reduce the need for it. - + - We only create the polynomial ideal as needed. - For some operations, we try some superficial tests first. E.g., for @@ -98,14 +98,14 @@ def set_hint(self, hint): """ Set the hint of this ideal. - The hint is an ideal of the associated polynomial ring, which is + The hint is an ideal of the associated polynomial ring, which is assumed to be contained in the associated ideal. It is used internally to speed up computation of the associated ideal in some cases; normally the end user will have no need to work with it directly. sage: P. = LaurentPolynomialRing(QQ, 3) sage: I = P.ideal([x^2*y + 3*x*y^2]) - sage: I.hint() + sage: I.hint() Ideal (0) of Multivariate Polynomial Ring in x, y, z over Rational Field sage: I.set_hint(P.polynomial_ring().ideal([x + 3*y])) sage: I.hint() @@ -117,7 +117,7 @@ def hint(self): """ Return the hint of this ideal. - The hint is an ideal of the associated polynomial ring, which is + The hint is an ideal of the associated polynomial ring, which is assumed to be contained in the associated ideal. It is used internally to speed up computation of the associated ideal in some cases; normally the end user will have no need to work with it directly. @@ -126,7 +126,7 @@ def hint(self): sage: P. = LaurentPolynomialRing(QQ, 3) sage: I = P.ideal([x^2*y + 3*x*y^2]) - sage: I.hint() + sage: I.hint() Ideal (0) of Multivariate Polynomial Ring in x, y, z over Rational Field """ return self._hint @@ -198,8 +198,8 @@ def __contains__(self, f): def change_ring(self, R, hint=None): """ Coerce an ideal into a new ring. - - This operation does not forward hints, but a new hint can be + + This operation does not forward hints, but a new hint can be specified manually. EXAMPLES:: @@ -319,7 +319,7 @@ def __add__(self, other): Return the sum of two ideals in the same ring. Currently this operation does not support coercion. - + This operation forwards hints. EXAMPLES:: @@ -367,10 +367,10 @@ def normalize_gens(self): def polynomial_ideal(self, saturate=True): """ Return the associated polynomial ideal. - + By default, the ideal is saturated with respect to the product of the polynomial ring generators; this is necessary for testing equality and inclusion. - As saturation can be quite time-consuming, it can be disabled by setting + As saturation can be quite time-consuming, it can be disabled by setting ``saturate=False``; however, the result will then depend not just on the original ideal but also on the choice of generators. @@ -441,7 +441,7 @@ def is_binomial(self, groebner_basis=False): """ Determine whether every generator of ``self`` is a binomial. - If ``groebner_basis`` is True, this becomes intrinsic (for a choice of + If ``groebner_basis`` is True, this becomes intrinsic (for a choice of term order). EXAMPLES:: diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring.py b/src/sage/rings/polynomial/laurent_polynomial_ring.py index 9f9948bb84b..c10faf46568 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring.py @@ -730,7 +730,7 @@ def ideal(self, *args, **kwds): Ideal (1) of Multivariate Laurent Polynomial Ring in x0, x1 over Rational Field TESTS: - + check that :trac:`26421` is fixed: sage: R. = LaurentPolynomialRing(ZZ) diff --git a/src/sage/rings/semirings/tropical_semiring.pyx b/src/sage/rings/semirings/tropical_semiring.pyx index 17e25091f39..f0776b79a0a 100644 --- a/src/sage/rings/semirings/tropical_semiring.pyx +++ b/src/sage/rings/semirings/tropical_semiring.pyx @@ -189,7 +189,7 @@ cdef class TropicalSemiringElement(Element): cdef TropicalSemiringElement self, x self = left x = right - + if self._val is None: if x._val is None: return rich_to_bool(op, 0) diff --git a/src/sage/rings/tate_algebra_ideal.pyx b/src/sage/rings/tate_algebra_ideal.pyx index 8826f67d000..5ce909911b9 100644 --- a/src/sage/rings/tate_algebra_ideal.pyx +++ b/src/sage/rings/tate_algebra_ideal.pyx @@ -874,7 +874,7 @@ def groebner_basis_pote(I, prec, verbose=0): # TODO: this should probably be a single function call f = f.monic() << f.valuation() - + if verbose > 0: print("---") print("new generator: %s + ..." % f.leading_term()) @@ -1019,7 +1019,7 @@ def groebner_basis_pote(I, prec, verbose=0): gb.sort(reverse=True) return gb - + def groebner_basis_vapote(I, prec, verbose=0, interrupt_red_with_val=False, interrupt_interred_with_val=False): r""" Run the VaPoTe algorithm to compute the Groebner basis of ``I`` @@ -1158,7 +1158,7 @@ def groebner_basis_vapote(I, prec, verbose=0, interrupt_red_with_val=False, inte continue f = f.monic() << f.valuation() - + if f and f.valuation() > val: if verbose > 0: print("reduction increases the valuation") diff --git a/src/sage/sat/solvers/cryptominisat.py b/src/sage/sat/solvers/cryptominisat.py index eb12e9fbbb3..d2a8ae9e5a6 100644 --- a/src/sage/sat/solvers/cryptominisat.py +++ b/src/sage/sat/solvers/cryptominisat.py @@ -94,10 +94,11 @@ def var(self, decision=None): def nvars(self): r""" - Return the number of variables. Note that for compatibility with DIMACS - convention, the number of variables corresponds to the maximal index of - the variables used. - + Return the number of variables. + + Note that for compatibility with DIMACS convention, the number + of variables corresponds to the maximal index of the variables used. + EXAMPLES:: sage: from sage.sat.solvers.cryptominisat import CryptoMiniSat diff --git a/src/sage/sat/solvers/picosat.py b/src/sage/sat/solvers/picosat.py index daeef620669..a1a80e71794 100644 --- a/src/sage/sat/solvers/picosat.py +++ b/src/sage/sat/solvers/picosat.py @@ -85,10 +85,11 @@ def var(self, decision=None): def nvars(self): r""" - Return the number of variables. Note that for compatibility with DIMACS - convention, the number of variables corresponds to the maximal index of - the variables used. - + Return the number of variables. + + Note that for compatibility with DIMACS convention, the number + of variables corresponds to the maximal index of the variables used. + EXAMPLES:: sage: from sage.sat.solvers.picosat import PicoSAT diff --git a/src/sage/sat/solvers/satsolver.pyx b/src/sage/sat/solvers/satsolver.pyx index d93181c142f..5baec390395 100644 --- a/src/sage/sat/solvers/satsolver.pyx +++ b/src/sage/sat/solvers/satsolver.pyx @@ -328,7 +328,7 @@ def SAT(solver=None, *args, **kwds): - ``"picosat"`` -- note that the pycosat package must be installed. - ``"glucose"`` -- note that the glucose package must be installed. - + - ``"glucose-syrup"`` -- note that the glucose package must be installed. - ``"LP"`` -- use :class:`~sage.sat.solvers.sat_lp.SatLP` to solve the diff --git a/src/sage/schemes/elliptic_curves/padics.py b/src/sage/schemes/elliptic_curves/padics.py index d4b38156669..afe9d6c52d1 100644 --- a/src/sage/schemes/elliptic_curves/padics.py +++ b/src/sage/schemes/elliptic_curves/padics.py @@ -178,10 +178,10 @@ def padic_lseries(self, p, normalize=None, implementation='eclib', sage: L = e.padic_lseries(3, implementation = 'sage') sage: L.series(5,prec=10) 2 + 3 + 3^2 + 2*3^3 + 2*3^5 + 3^6 + O(3^7) + (1 + 3 + 2*3^2 + 3^3 + O(3^4))*T + (1 + 2*3 + O(3^4))*T^2 + (3 + 2*3^2 + O(3^3))*T^3 + (2*3 + 3^2 + O(3^3))*T^4 + (2 + 2*3 + 2*3^2 + O(3^3))*T^5 + (1 + 3^2 + O(3^3))*T^6 + (2 + 3^2 + O(3^3))*T^7 + (2 + 2*3 + 2*3^2 + O(3^3))*T^8 + (2 + O(3^2))*T^9 + O(T^10) - + Also the numerical modular symbols can be used. - This may allow for much larger conductor in some instances:: - + This may allow for much larger conductor in some instances:: + sage: E = EllipticCurve([101,103]) sage: L = E.padic_lseries(5, implementation="num") sage: L.series(2) diff --git a/src/sage/schemes/product_projective/rational_point.py b/src/sage/schemes/product_projective/rational_point.py index 8c737f14e0b..3bd717ee39e 100644 --- a/src/sage/schemes/product_projective/rational_point.py +++ b/src/sage/schemes/product_projective/rational_point.py @@ -189,7 +189,7 @@ def enum_product_projective_number_field(X, **kwds): This is an implementation of the revised algorithm (Algorithm 4) in [DK2013]_. Algorithm 5 is used for imaginary quadratic fields. - + INPUT: kwds: diff --git a/src/sage/schemes/projective/projective_rational_point.py b/src/sage/schemes/projective/projective_rational_point.py index cb12e30c873..738f28cb993 100644 --- a/src/sage/schemes/projective/projective_rational_point.py +++ b/src/sage/schemes/projective/projective_rational_point.py @@ -167,7 +167,7 @@ def enum_projective_number_field(X, **kwds): This is an implementation of the revised algorithm (Algorithm 4) in [DK2013]_. Algorithm 5 is used for imaginary quadratic fields. - + INPUT: kwds: diff --git a/src/sage/tests/books/judson-abstract-algebra/boolean-sage.py b/src/sage/tests/books/judson-abstract-algebra/boolean-sage.py index daa0f3f6954..460403b3efb 100644 --- a/src/sage/tests/books/judson-abstract-algebra/boolean-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/boolean-sage.py @@ -56,7 +56,6 @@ sage: D = Poset([X, R]) sage: D.plot() # not tested - ~~~~~~~~~~~~~~~~~~~~~~ :: @@ -73,7 +72,6 @@ sage: Q = Posets.PentagonPoset() sage: Q.plot() # not tested - ~~~~~~~~~~~~~~~~~~~~~~ :: diff --git a/src/sage/tests/books/judson-abstract-algebra/rings-sage.py b/src/sage/tests/books/judson-abstract-algebra/rings-sage.py index 67ed4823924..6d9571d00cd 100644 --- a/src/sage/tests/books/judson-abstract-algebra/rings-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/rings-sage.py @@ -90,7 +90,6 @@ sage: F. = FiniteField(3^2) sage: P. = Z7[] sage: S. = QuaternionAlgebra(-7, 3) - ~~~~~~~~~~~~~~~~~~~~~~ :: From fda90859c17b01922d18086b64ad86bbca8f6948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 30 Oct 2022 21:38:41 +0100 Subject: [PATCH 280/414] fix and activate E306 and W605 in pyx files --- src/sage/graphs/strongly_regular_db.pyx | 1 + .../rings/padics/local_generic_element.pyx | 4 ++-- .../rings/padics/padic_ZZ_pX_CA_element.pyx | 8 +++---- .../rings/padics/padic_ZZ_pX_CR_element.pyx | 6 +++--- .../padics/padic_capped_absolute_element.pyx | 8 +++---- .../rings/padics/padic_fixed_mod_element.pyx | 8 +++---- .../rings/padics/padic_generic_element.pyx | 21 ++++++++++--------- src/sage/rings/padics/pow_computer_ext.pyx | 3 ++- src/sage/rings/padics/qadic_flint_CA.pyx | 4 ++-- src/sage/rings/padics/qadic_flint_CR.pyx | 4 ++-- src/sage/rings/padics/qadic_flint_FM.pyx | 4 ++-- src/sage/rings/padics/qadic_flint_FP.pyx | 4 ++-- src/tox.ini | 2 +- 13 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index 1e01738662d..8cf2c73c957 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -1213,6 +1213,7 @@ def SRG_from_RSHCD(v, k, l, mu, existence=False, check=True): ValueError: I do not know how to build a (784, 0, 14, 38)-SRG from a RSHCD """ from sage.combinat.matrices.hadamard_matrix import regular_symmetric_hadamard_matrix_with_constant_diagonal + def sgn(x): return 1 if x >= 0 else -1 n = v diff --git a/src/sage/rings/padics/local_generic_element.pyx b/src/sage/rings/padics/local_generic_element.pyx index 5923228bb69..0733e0a483f 100644 --- a/src/sage/rings/padics/local_generic_element.pyx +++ b/src/sage/rings/padics/local_generic_element.pyx @@ -408,8 +408,8 @@ cdef class LocalGenericElement(CommutativeRingElement): return ans def _latex_(self): - """ - Returns a latex representation of self. + r""" + Return a latex representation of self. EXAMPLES:: diff --git a/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx index 731d27ec33c..2b12b36cbed 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx @@ -4,7 +4,7 @@ # distutils: library_dirs = NTL_LIBDIR # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -""" +r""" `p`-adic ``ZZ_pX`` CA Element This file implements elements of Eisenstein and unramified extensions @@ -1546,7 +1546,7 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): return self.to_fraction_field() * (~right) def _integer_(self, Z=None): - """ + r""" Returns an integer congruent to this element modulo `\pi`^``self.absolute_precision()``, if possible. @@ -1951,8 +1951,8 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): return [zero] * ordp + ulist def matrix_mod_pn(self): - """ - Returns the matrix of right multiplication by the element on + r""" + Return the matrix of right multiplication by the element on the power basis `1, x, x^2, \ldots, x^{d-1}` for this extension field. Thus the *rows* of this matrix give the images of each of the `x^i`. The entries of the matrices are diff --git a/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx index 463cc371031..116e8a3a1db 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx @@ -4,7 +4,7 @@ # distutils: library_dirs = NTL_LIBDIR # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -""" +r""" `p`-adic ``ZZ_pX`` CR Element This file implements elements of Eisenstein and unramified extensions @@ -230,7 +230,7 @@ cdef inline int check_ordp(long a) except -1: cdef class pAdicZZpXCRElement(pAdicZZpXElement): def __init__(self, parent, x, absprec = infinity, relprec = infinity, empty = False): - """ + r""" Creates an element of a capped relative precision, unramified or Eisenstein extension of `\ZZ_p` or `\QQ_p`. @@ -2833,7 +2833,7 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): return ulist def matrix_mod_pn(self): - """ + r""" Return the matrix of right multiplication by the element on the power basis `1, x, x^2, \ldots, x^{d-1}` for this extension field. Thus the *rows* of this matrix give the diff --git a/src/sage/rings/padics/padic_capped_absolute_element.pyx b/src/sage/rings/padics/padic_capped_absolute_element.pyx index 91e973ccf7e..e48abd895ae 100644 --- a/src/sage/rings/padics/padic_capped_absolute_element.pyx +++ b/src/sage/rings/padics/padic_capped_absolute_element.pyx @@ -383,7 +383,7 @@ cdef class pAdicCappedAbsoluteElement(CAElement): return ans def _exp_binary_splitting(self, aprec): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. @@ -393,7 +393,7 @@ cdef class pAdicCappedAbsoluteElement(CAElement): - ``aprec`` -- an integer, the precision to which to compute the exponential - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not @@ -443,7 +443,7 @@ cdef class pAdicCappedAbsoluteElement(CAElement): return ans def _exp_newton(self, aprec, log_algorithm=None): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. @@ -458,7 +458,7 @@ cdef class pAdicCappedAbsoluteElement(CAElement): method. See :meth:`log` for more details about the possible algorithms. - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not diff --git a/src/sage/rings/padics/padic_fixed_mod_element.pyx b/src/sage/rings/padics/padic_fixed_mod_element.pyx index 8b45f6cea71..a53b0da7525 100644 --- a/src/sage/rings/padics/padic_fixed_mod_element.pyx +++ b/src/sage/rings/padics/padic_fixed_mod_element.pyx @@ -157,7 +157,7 @@ cdef class pAdicFixedModElement(FMElement): return self.lift_c() cdef lift_c(self): - """ + r""" Returns an integer congruent to this element modulo the precision. .. WARNING:: @@ -227,7 +227,7 @@ cdef class pAdicFixedModElement(FMElement): holder.value) def _integer_(self, Z=None): - """ + r""" Return an integer congruent to ``self`` modulo the precision. .. WARNING:: @@ -449,7 +449,7 @@ cdef class pAdicFixedModElement(FMElement): return ans def _exp_binary_splitting(self, aprec): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. @@ -508,7 +508,7 @@ cdef class pAdicFixedModElement(FMElement): return ans def _exp_newton(self, aprec, log_algorithm=None): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 5cf7d8a04f3..4248994b88e 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -544,7 +544,7 @@ cdef class pAdicGenericElement(LocalGenericElement): return self._repr_(mode=mode) def _repr_(self, mode=None, do_latex=False): - """ + r""" Returns a string representation of this element. INPUT: @@ -565,7 +565,6 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: K. = Qp(2).extension(x^3 - 2) sage: latex(pi) \pi + O(\pi^{61}) - """ return self.parent()._printer.repr_gen(self, do_latex, mode=mode) @@ -3028,7 +3027,7 @@ cdef class pAdicGenericElement(LocalGenericElement): return series_unit*nfactorial_unit.inverse_of_unit()<<(series_val-nfactorial_val) def _exp_binary_splitting(self, aprec): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. @@ -3038,7 +3037,7 @@ cdef class pAdicGenericElement(LocalGenericElement): - ``aprec`` -- an integer, the precision to which to compute the exponential - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not @@ -3074,7 +3073,7 @@ cdef class pAdicGenericElement(LocalGenericElement): raise NotImplementedError("the binary splitting algorithm is not implemented for the parent: %s" % self.parent()) def _exp_newton(self, aprec, log_algorithm=None): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. @@ -3089,7 +3088,7 @@ cdef class pAdicGenericElement(LocalGenericElement): method. See :meth:`log` for more details about the possible algorithms. - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not @@ -3783,7 +3782,7 @@ cdef class pAdicGenericElement(LocalGenericElement): return parent(root) def _inverse_pth_root(self, twist=None, hint=None): - """ + r""" In its simplest form, computes the inverse of ``p``-th root of this element. @@ -4409,8 +4408,9 @@ def _polylog_c(n, p): """ return p/(p-1) - (n-1)/p.log().n() + (n-1)*(n*(p-1)/p.log().n()).log(p).n() + (2*p*(p-1)*n/p.log().n()).log(p).n() + def _findprec(c_1, c_2, c_3, p): - """ + r""" Return an integer k such that c_1*k - c_2*log_p(k) > c_3. This is an internal function, used by :meth:`polylog`. @@ -4439,16 +4439,17 @@ def _findprec(c_1, c_2, c_3, p): return k k += 1 + def _compute_g(p, n, prec, terms): - """ + r""" Return the list of power series `g_i = \int(-g_{i-1}/(v-v^2))` used in the computation of polylogarithms. + This is an internal function, used by :meth:`polylog`. EXAMPLES:: sage: sage.rings.padics.padic_generic_element._compute_g(7, 3, 3, 3)[0] O(7^3)*v^2 + (1 + O(7^3))*v + O(7^3) - """ from sage.rings.power_series_ring import PowerSeriesRing from sage.functions.other import ceil diff --git a/src/sage/rings/padics/pow_computer_ext.pyx b/src/sage/rings/padics/pow_computer_ext.pyx index 7dc2887337d..fe60e6bde33 100644 --- a/src/sage/rings/padics/pow_computer_ext.pyx +++ b/src/sage/rings/padics/pow_computer_ext.pyx @@ -1214,8 +1214,9 @@ cdef class PowComputer_ZZ_pX(PowComputer_ext): ZZ_pX_add(xnew_q, xnew_q, x[0]) return 0 + cdef class PowComputer_ZZ_pX_FM(PowComputer_ZZ_pX): - """ + r""" This class only caches a context and modulus for p^prec_cap. Designed for use with fixed modulus p-adic rings, in Eisenstein diff --git a/src/sage/rings/padics/qadic_flint_CA.pyx b/src/sage/rings/padics/qadic_flint_CA.pyx index d10e4b3eaff..3e4d01fcad1 100644 --- a/src/sage/rings/padics/qadic_flint_CA.pyx +++ b/src/sage/rings/padics/qadic_flint_CA.pyx @@ -27,8 +27,8 @@ cdef class qAdicCappedAbsoluteElement(CAElement): norm = norm_unram def matrix_mod_pn(self): - """ - Returns the matrix of right multiplication by the element on + r""" + Return the matrix of right multiplication by the element on the power basis `1, x, x^2, \ldots, x^{d-1}` for this extension field. Thus the *rows* of this matrix give the images of each of the `x^i`. The entries of the matrices are diff --git a/src/sage/rings/padics/qadic_flint_CR.pyx b/src/sage/rings/padics/qadic_flint_CR.pyx index dfadaa2f053..c20fa087424 100644 --- a/src/sage/rings/padics/qadic_flint_CR.pyx +++ b/src/sage/rings/padics/qadic_flint_CR.pyx @@ -27,8 +27,8 @@ cdef class qAdicCappedRelativeElement(CRElement): norm = norm_unram def matrix_mod_pn(self): - """ - Returns the matrix of right multiplication by the element on + r""" + Return the matrix of right multiplication by the element on the power basis `1, x, x^2, \ldots, x^{d-1}` for this extension field. Thus the *rows* of this matrix give the images of each of the `x^i`. The entries of the matrices are diff --git a/src/sage/rings/padics/qadic_flint_FM.pyx b/src/sage/rings/padics/qadic_flint_FM.pyx index 4f3afe77e81..0a5e0563101 100644 --- a/src/sage/rings/padics/qadic_flint_FM.pyx +++ b/src/sage/rings/padics/qadic_flint_FM.pyx @@ -27,8 +27,8 @@ cdef class qAdicFixedModElement(FMElement): norm = norm_unram def matrix_mod_pn(self): - """ - Returns the matrix of right multiplication by the element on + r""" + Return the matrix of right multiplication by the element on the power basis `1, x, x^2, \ldots, x^{d-1}` for this extension field. Thus the *rows* of this matrix give the images of each of the `x^i`. The entries of the matrices are diff --git a/src/sage/rings/padics/qadic_flint_FP.pyx b/src/sage/rings/padics/qadic_flint_FP.pyx index 1e250990c4e..020b03b722a 100644 --- a/src/sage/rings/padics/qadic_flint_FP.pyx +++ b/src/sage/rings/padics/qadic_flint_FP.pyx @@ -29,8 +29,8 @@ cdef class qAdicFloatingPointElement(FPElement): norm = norm_unram def matrix_mod_pn(self): - """ - Returns the matrix of right multiplication by the element on + r""" + Return the matrix of right multiplication by the element on the power basis `1, x, x^2, \ldots, x^{d-1}` for this extension field. Thus the *rows* of this matrix give the images of each of the `x^i`. The entries of the matrices are diff --git a/src/tox.ini b/src/tox.ini index 06ab0b18a0d..1fdf5230aae 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -111,7 +111,7 @@ description = # See https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes deps = pycodestyle commands = pycodestyle --select E111,E306,E401,E701,E702,E703,W391,W605,E711,E712,E713,E721,E722 {posargs:{toxinidir}/sage/} - pycodestyle --select E111,E401,E703,E712,E713,E721,E722 --filename *.pyx {posargs:{toxinidir}/sage/} + pycodestyle --select E111,E306,E401,E703,W605,E712,E713,E721,E722 --filename *.pyx {posargs:{toxinidir}/sage/} [pycodestyle] max-line-length = 160 From f6faca25071257804593d44278ab6c50a9aa51cc Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:34:39 -0700 Subject: [PATCH 281/414] build/pkgs/pip: Update to 22.3 --- build/pkgs/pip/checksums.ini | 6 +++--- build/pkgs/pip/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pip/checksums.ini b/build/pkgs/pip/checksums.ini index a13af9996c7..cc58418441f 100644 --- a/build/pkgs/pip/checksums.ini +++ b/build/pkgs/pip/checksums.ini @@ -1,5 +1,5 @@ tarball=pip-VERSION.tar.gz -sha1=ed6e6d191a686b4f989a1dbb2640737a1123f24f -md5=05bb8c0607721d171e9eecf22a8c5cc6 -cksum=4023376185 +sha1=a6d9d6b04f17acb11f214db9835f4b66e12a1fe2 +md5=f0dd02265e7ccd2f8758c840fba64810 +cksum=1176625017 upstream_url=https://pypi.io/packages/source/p/pip/pip-VERSION.tar.gz diff --git a/build/pkgs/pip/package-version.txt b/build/pkgs/pip/package-version.txt index 637c2a16439..937387f33bb 100644 --- a/build/pkgs/pip/package-version.txt +++ b/build/pkgs/pip/package-version.txt @@ -1 +1 @@ -22.2.2 +22.3 From 89a7f5248e004ea84f68989cb9126b545f69da25 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:35:38 -0700 Subject: [PATCH 282/414] build/pkgs/certifi: Update to 2022.9.24 --- build/pkgs/certifi/checksums.ini | 6 +++--- build/pkgs/certifi/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/certifi/checksums.ini b/build/pkgs/certifi/checksums.ini index 87d8378aa08..cab6201a644 100644 --- a/build/pkgs/certifi/checksums.ini +++ b/build/pkgs/certifi/checksums.ini @@ -1,5 +1,5 @@ tarball=certifi-VERSION.tar.gz -sha1=b13e22d55867e2ca5f92e5289cfdc21ba6e343aa -md5=880ed9e5d04aff8f46f5ff82a3a3e395 -cksum=613361382 +sha1=4a6fb9ae2afe62b33bab98ae21c0853d026d64c2 +md5=ff9c8d5c7e7fb083de6b874609c5ca68 +cksum=726096582 upstream_url=https://pypi.io/packages/source/c/certifi/certifi-VERSION.tar.gz diff --git a/build/pkgs/certifi/package-version.txt b/build/pkgs/certifi/package-version.txt index 6b1fb396ceb..e4b8493d095 100644 --- a/build/pkgs/certifi/package-version.txt +++ b/build/pkgs/certifi/package-version.txt @@ -1 +1 @@ -2021.10.8 +2022.9.24 From 773f2eb51bd9d2acb60fde850a582bc65c495ca5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:36:18 -0700 Subject: [PATCH 283/414] build/pkgs/filelock: Update to 3.8.0 --- build/pkgs/filelock/checksums.ini | 6 +++--- build/pkgs/filelock/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/filelock/checksums.ini b/build/pkgs/filelock/checksums.ini index 24a5e470bef..48a18055930 100644 --- a/build/pkgs/filelock/checksums.ini +++ b/build/pkgs/filelock/checksums.ini @@ -1,5 +1,5 @@ tarball=filelock-VERSION.tar.gz -sha1=e0340015dcb7bbe19b5bf33ff8b9c94670994585 -md5=b1032075ddada92874377426337c38a6 -cksum=3903629392 +sha1=1de304add05b7e3e8874aa9f86202204f8042e30 +md5=9bd8d33d5d7dc95012981ccbfb2d2a0f +cksum=2335245752 upstream_url=https://pypi.io/packages/source/f/filelock/filelock-VERSION.tar.gz diff --git a/build/pkgs/filelock/package-version.txt b/build/pkgs/filelock/package-version.txt index 40c341bdcdb..19811903a7f 100644 --- a/build/pkgs/filelock/package-version.txt +++ b/build/pkgs/filelock/package-version.txt @@ -1 +1 @@ -3.6.0 +3.8.0 From 2b9c26fc5d779c51af12e943b3e15f8df121dec6 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:36:37 -0700 Subject: [PATCH 284/414] build/pkgs/charset_normalizer: Update to 2.1.1 --- build/pkgs/charset_normalizer/checksums.ini | 6 +++--- build/pkgs/charset_normalizer/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/charset_normalizer/checksums.ini b/build/pkgs/charset_normalizer/checksums.ini index 90d2c47aba4..714de114bec 100644 --- a/build/pkgs/charset_normalizer/checksums.ini +++ b/build/pkgs/charset_normalizer/checksums.ini @@ -1,5 +1,5 @@ tarball=charset-normalizer-VERSION.tar.gz -sha1=6824bfae6dec62d93887b53468ea36124db5ecc8 -md5=f6664e0e90dbb3cc9cfc154a980f9864 -cksum=2680691552 +sha1=f976f0ee784273ee6bc06e996fbc192cbb718d18 +md5=a70f9fc85b6b8265c982eca6fe51381f +cksum=1911107732 upstream_url=https://pypi.io/packages/source/c/charset_normalizer/charset-normalizer-VERSION.tar.gz diff --git a/build/pkgs/charset_normalizer/package-version.txt b/build/pkgs/charset_normalizer/package-version.txt index 280a1e3368b..3e3c2f1e5ed 100644 --- a/build/pkgs/charset_normalizer/package-version.txt +++ b/build/pkgs/charset_normalizer/package-version.txt @@ -1 +1 @@ -2.0.12 +2.1.1 From 8de5b419b659e4821bbdb2d7dc072269cd97353a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:36:46 -0700 Subject: [PATCH 285/414] build/pkgs/idna: Update to 3.4 --- build/pkgs/idna/checksums.ini | 6 +++--- build/pkgs/idna/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/idna/checksums.ini b/build/pkgs/idna/checksums.ini index d0e20f2098a..47b585b69cd 100644 --- a/build/pkgs/idna/checksums.ini +++ b/build/pkgs/idna/checksums.ini @@ -1,5 +1,5 @@ tarball=idna-VERSION.tar.gz -sha1=08c0449533fc94462f78652dea209099754d9ee4 -md5=5856306eac5f25db8249e37a4c6ee3e7 -cksum=2700709091 +sha1=c01a061b5ace87f662049d205d5d15e7f8a3a533 +md5=13ea24e076212b6baae1135a116d1e0e +cksum=1497605198 upstream_url=https://pypi.io/packages/source/i/idna/idna-VERSION.tar.gz diff --git a/build/pkgs/idna/package-version.txt b/build/pkgs/idna/package-version.txt index eb39e5382f4..2f4b60750dc 100644 --- a/build/pkgs/idna/package-version.txt +++ b/build/pkgs/idna/package-version.txt @@ -1 +1 @@ -3.3 +3.4 From 48747df5c665b3aae7bb45018a550ed4a4613eed Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:37:47 -0700 Subject: [PATCH 286/414] build/pkgs/importlib_metadata: Update to 5.0.0 --- build/pkgs/importlib_metadata/checksums.ini | 6 +++--- build/pkgs/importlib_metadata/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/importlib_metadata/checksums.ini b/build/pkgs/importlib_metadata/checksums.ini index ea39c54a4a3..c6d66d9ee23 100644 --- a/build/pkgs/importlib_metadata/checksums.ini +++ b/build/pkgs/importlib_metadata/checksums.ini @@ -1,5 +1,5 @@ tarball=importlib_metadata-VERSION.tar.gz -sha1=ec68de1ec1800048de8656b9d211e22b7fe7c53e -md5=cfcf29185e13439c76d09c94bc8d81f4 -cksum=2134804316 +sha1=38794db2afb90ed0be04bd8b5996a5b5fd45acc2 +md5=ccd58a387cc2bab6cf72fdf21e403749 +cksum=758044022 upstream_url=https://pypi.io/packages/source/i/importlib_metadata/importlib_metadata-VERSION.tar.gz diff --git a/build/pkgs/importlib_metadata/package-version.txt b/build/pkgs/importlib_metadata/package-version.txt index 815588ef140..0062ac97180 100644 --- a/build/pkgs/importlib_metadata/package-version.txt +++ b/build/pkgs/importlib_metadata/package-version.txt @@ -1 +1 @@ -4.12.0 +5.0.0 From 92f544fc726aa162ed688e0977b73654ad1de2fb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:37:52 -0700 Subject: [PATCH 287/414] build/pkgs/importlib_resources: Update to 5.10.0 --- build/pkgs/importlib_resources/checksums.ini | 6 +++--- build/pkgs/importlib_resources/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/importlib_resources/checksums.ini b/build/pkgs/importlib_resources/checksums.ini index 99a4cdf7908..9885db7ffb4 100644 --- a/build/pkgs/importlib_resources/checksums.ini +++ b/build/pkgs/importlib_resources/checksums.ini @@ -1,5 +1,5 @@ tarball=importlib_resources-VERSION.tar.gz -sha1=3b4b20fa0399e2fa21c7506be27a4b943495d3ad -md5=3b6d98270d40b2ba7af1f8d09188f0c2 -cksum=2401793228 +sha1=a8c7a6a976fffb9841c548230cb633eda3111c4f +md5=8afc48c5f3a7c4ba63cb38163340d78b +cksum=196052500 upstream_url=https://pypi.io/packages/source/i/importlib_resources/importlib_resources-VERSION.tar.gz diff --git a/build/pkgs/importlib_resources/package-version.txt b/build/pkgs/importlib_resources/package-version.txt index b3d91f9cfc0..509b0b618ad 100644 --- a/build/pkgs/importlib_resources/package-version.txt +++ b/build/pkgs/importlib_resources/package-version.txt @@ -1 +1 @@ -5.9.0 +5.10.0 From a8e50429efe1dc91ee5a37178d89c66d020870e4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:38:36 -0700 Subject: [PATCH 288/414] build/pkgs/jsonschema: Update to 4.16.0 --- build/pkgs/jsonschema/checksums.ini | 6 +++--- build/pkgs/jsonschema/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/jsonschema/checksums.ini b/build/pkgs/jsonschema/checksums.ini index 955e0401faa..b25e78e04fa 100644 --- a/build/pkgs/jsonschema/checksums.ini +++ b/build/pkgs/jsonschema/checksums.ini @@ -1,5 +1,5 @@ tarball=jsonschema-VERSION.tar.gz -sha1=5f90a208235152dc4f0b8ae51ca2860c0771ff51 -md5=c3f7a29c187bf1d038c66a5d5763eab1 -cksum=2329724463 +sha1=912d562c1394408dca582e14843e3245df2f3827 +md5=3bc1f63a74fc61bf2847f22cadb0dfff +cksum=2200261176 upstream_url=https://pypi.io/packages/source/j/jsonschema/jsonschema-VERSION.tar.gz diff --git a/build/pkgs/jsonschema/package-version.txt b/build/pkgs/jsonschema/package-version.txt index 4404a17baed..ecbc3b03079 100644 --- a/build/pkgs/jsonschema/package-version.txt +++ b/build/pkgs/jsonschema/package-version.txt @@ -1 +1 @@ -4.5.1 +4.16.0 From a0039dbc67cdf1528960ec57c493e220befb5d05 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 18 Oct 2022 12:41:17 -0700 Subject: [PATCH 289/414] build/pkgs/hatch_fancy_pypi_readme: New (jsonschema build dep) --- build/pkgs/hatch_fancy_pypi_readme/SPKG.rst | 18 ++++++++++++++++++ .../pkgs/hatch_fancy_pypi_readme/checksums.ini | 5 +++++ .../pkgs/hatch_fancy_pypi_readme/dependencies | 4 ++++ .../install-requires.txt | 1 + .../package-version.txt | 1 + .../hatch_fancy_pypi_readme/spkg-install.in | 2 ++ build/pkgs/hatch_fancy_pypi_readme/type | 1 + build/pkgs/jsonschema/dependencies | 2 +- 8 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/hatch_fancy_pypi_readme/SPKG.rst create mode 100644 build/pkgs/hatch_fancy_pypi_readme/checksums.ini create mode 100644 build/pkgs/hatch_fancy_pypi_readme/dependencies create mode 100644 build/pkgs/hatch_fancy_pypi_readme/install-requires.txt create mode 100644 build/pkgs/hatch_fancy_pypi_readme/package-version.txt create mode 100644 build/pkgs/hatch_fancy_pypi_readme/spkg-install.in create mode 100644 build/pkgs/hatch_fancy_pypi_readme/type diff --git a/build/pkgs/hatch_fancy_pypi_readme/SPKG.rst b/build/pkgs/hatch_fancy_pypi_readme/SPKG.rst new file mode 100644 index 00000000000..4e076e4e3cb --- /dev/null +++ b/build/pkgs/hatch_fancy_pypi_readme/SPKG.rst @@ -0,0 +1,18 @@ +hatch_fancy_pypi_readme: Fancy PyPI READMEs with Hatch +====================================================== + +Description +----------- + +Fancy PyPI READMEs with Hatch + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/hatch-fancy-pypi-readme/ + diff --git a/build/pkgs/hatch_fancy_pypi_readme/checksums.ini b/build/pkgs/hatch_fancy_pypi_readme/checksums.ini new file mode 100644 index 00000000000..6728a45be5c --- /dev/null +++ b/build/pkgs/hatch_fancy_pypi_readme/checksums.ini @@ -0,0 +1,5 @@ +tarball=hatch_fancy_pypi_readme-VERSION.tar.gz +sha1=2cdf215fdd13de69f5de09c7ef0e2ceff4a03666 +md5=588776ea8e3608714d4cbba16dffa92b +cksum=613442646 +upstream_url=https://pypi.io/packages/source/h/hatch_fancy_pypi_readme/hatch_fancy_pypi_readme-VERSION.tar.gz diff --git a/build/pkgs/hatch_fancy_pypi_readme/dependencies b/build/pkgs/hatch_fancy_pypi_readme/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/hatch_fancy_pypi_readme/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/hatch_fancy_pypi_readme/install-requires.txt b/build/pkgs/hatch_fancy_pypi_readme/install-requires.txt new file mode 100644 index 00000000000..6d9a1f85903 --- /dev/null +++ b/build/pkgs/hatch_fancy_pypi_readme/install-requires.txt @@ -0,0 +1 @@ +hatch-fancy-pypi-readme diff --git a/build/pkgs/hatch_fancy_pypi_readme/package-version.txt b/build/pkgs/hatch_fancy_pypi_readme/package-version.txt new file mode 100644 index 00000000000..9d673278df8 --- /dev/null +++ b/build/pkgs/hatch_fancy_pypi_readme/package-version.txt @@ -0,0 +1 @@ +22.8.0 diff --git a/build/pkgs/hatch_fancy_pypi_readme/spkg-install.in b/build/pkgs/hatch_fancy_pypi_readme/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/hatch_fancy_pypi_readme/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/hatch_fancy_pypi_readme/type b/build/pkgs/hatch_fancy_pypi_readme/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/hatch_fancy_pypi_readme/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/jsonschema/dependencies b/build/pkgs/jsonschema/dependencies index f487bdbbe91..e488e4e4422 100644 --- a/build/pkgs/jsonschema/dependencies +++ b/build/pkgs/jsonschema/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) vcversioner attrs importlib_metadata pyrsistent | $(PYTHON_TOOLCHAIN) hatchling +$(PYTHON) vcversioner attrs importlib_metadata pyrsistent | $(PYTHON_TOOLCHAIN) hatchling hatch_fancy_pypi_readme ---------- All lines of this file are ignored except the first. From e9d639cd57c8ec8606d0af88bb675782a4944d31 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:39:06 -0700 Subject: [PATCH 290/414] build/pkgs/nest_asyncio: Update to 1.5.6 --- build/pkgs/nest_asyncio/checksums.ini | 6 +++--- build/pkgs/nest_asyncio/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/nest_asyncio/checksums.ini b/build/pkgs/nest_asyncio/checksums.ini index 3ffec6bed8b..1a11eb5178b 100644 --- a/build/pkgs/nest_asyncio/checksums.ini +++ b/build/pkgs/nest_asyncio/checksums.ini @@ -1,5 +1,5 @@ tarball=nest_asyncio-VERSION.tar.gz -sha1=ca7b9c57e544a7ca1e96d37ead648588cfb9a3eb -md5=0243278ed8804811b00049a545856dcb -cksum=3944772454 +sha1=1e862862afe4c2e057065212eefe7203ccee4927 +md5=7c7108921a64e7abbb6993803343819b +cksum=982689987 upstream_url=https://pypi.io/packages/source/n/nest_asyncio/nest_asyncio-VERSION.tar.gz diff --git a/build/pkgs/nest_asyncio/package-version.txt b/build/pkgs/nest_asyncio/package-version.txt index 9075be49515..eac1e0ada6d 100644 --- a/build/pkgs/nest_asyncio/package-version.txt +++ b/build/pkgs/nest_asyncio/package-version.txt @@ -1 +1 @@ -1.5.5 +1.5.6 From 40e60b630c407bf8d8bd808105310e884320c1db Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:39:32 -0700 Subject: [PATCH 291/414] build/pkgs/platformdirs: Update to 2.5.2 --- build/pkgs/platformdirs/checksums.ini | 6 +++--- build/pkgs/platformdirs/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/platformdirs/checksums.ini b/build/pkgs/platformdirs/checksums.ini index e6a6c0613a8..8dc4a85a66d 100644 --- a/build/pkgs/platformdirs/checksums.ini +++ b/build/pkgs/platformdirs/checksums.ini @@ -1,5 +1,5 @@ tarball=platformdirs-VERSION.tar.gz -sha1=16e9a89587b4471041c6d1a2444d200a92292c73 -md5=83d3ce3feb4af1ccfaca24375574f44d -cksum=1234653032 +sha1=344841a3cd4eb5b1a1b8adb8a57e845e5a06b236 +md5=2301a8a29c3082a49ee293073d893887 +cksum=1100125935 upstream_url=https://pypi.io/packages/source/p/platformdirs/platformdirs-VERSION.tar.gz diff --git a/build/pkgs/platformdirs/package-version.txt b/build/pkgs/platformdirs/package-version.txt index 73462a5a134..f225a78adf0 100644 --- a/build/pkgs/platformdirs/package-version.txt +++ b/build/pkgs/platformdirs/package-version.txt @@ -1 +1 @@ -2.5.1 +2.5.2 From e7f7639aa326705f89ccd8b3377fa48b607cedb4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:41:50 -0700 Subject: [PATCH 292/414] build/pkgs/pytz: Update to 2022.4 --- build/pkgs/pytz/checksums.ini | 6 +++--- build/pkgs/pytz/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pytz/checksums.ini b/build/pkgs/pytz/checksums.ini index 0faabac40af..0d031b316d0 100644 --- a/build/pkgs/pytz/checksums.ini +++ b/build/pkgs/pytz/checksums.ini @@ -1,5 +1,5 @@ tarball=pytz-VERSION.tar.gz -sha1=fa6729d40cfa607daee0f40cdab165976ab0e0a3 -md5=d7b7060bbac4970afa2050c139c9fcb6 -cksum=3161093227 +sha1=74975d8618b7c71bfc38958979ab96da4d9a3aba +md5=b1d2ed6592bbdf6002ef52b4ab8e2efe +cksum=1769835625 upstream_url=https://pypi.io/packages/source/p/pytz/pytz-VERSION.tar.gz diff --git a/build/pkgs/pytz/package-version.txt b/build/pkgs/pytz/package-version.txt index c9c8e05b0f8..a2a5548bc1c 100644 --- a/build/pkgs/pytz/package-version.txt +++ b/build/pkgs/pytz/package-version.txt @@ -1 +1 @@ -2021.3 +2022.4 From 670ae27ae2357ebc3737b72ec41f494c3114ad3d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:42:03 -0700 Subject: [PATCH 293/414] build/pkgs/requests: Update to 2.28.1 --- build/pkgs/requests/checksums.ini | 6 +++--- build/pkgs/requests/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/requests/checksums.ini b/build/pkgs/requests/checksums.ini index 4a2693a8d90..5aabe04f278 100644 --- a/build/pkgs/requests/checksums.ini +++ b/build/pkgs/requests/checksums.ini @@ -1,5 +1,5 @@ tarball=requests-VERSION.tar.gz -sha1=a72bdfba339f5058c051c71954b59bef94c84455 -md5=5c1f6e737e1cb6f86a91d7a1473eda95 -cksum=4021136359 +sha1=53381250a0d114109a9e712dd7ce8e40e63e61e2 +md5=796ea875cdae283529c03b9203d9c454 +cksum=4112189908 upstream_url=https://pypi.io/packages/source/r/requests/requests-VERSION.tar.gz diff --git a/build/pkgs/requests/package-version.txt b/build/pkgs/requests/package-version.txt index 90efbd4e31e..9738a24f699 100644 --- a/build/pkgs/requests/package-version.txt +++ b/build/pkgs/requests/package-version.txt @@ -1 +1 @@ -2.28.0 +2.28.1 From 73cfdaf41dcf5c50dfa334271c94559a3a214240 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:42:47 -0700 Subject: [PATCH 294/414] build/pkgs/tomlkit: Update to 0.11.5 --- build/pkgs/tomlkit/checksums.ini | 6 +++--- build/pkgs/tomlkit/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tomlkit/checksums.ini b/build/pkgs/tomlkit/checksums.ini index f43c80b7196..c5c309c6b4b 100644 --- a/build/pkgs/tomlkit/checksums.ini +++ b/build/pkgs/tomlkit/checksums.ini @@ -1,5 +1,5 @@ tarball=tomlkit-VERSION.tar.gz -sha1=65f56e209410e4eee4b45d048e6b4dc0fcaad74a -md5=d0edd43143c7840deb88185685cea8dd -cksum=846256591 +sha1=3d03c83e2dc36d2b1cef1bb82902f89ce639e2fe +md5=d5702dd3ecf513935d24d673761f5296 +cksum=3072168699 upstream_url=https://pypi.io/packages/source/t/tomlkit/tomlkit-VERSION.tar.gz diff --git a/build/pkgs/tomlkit/package-version.txt b/build/pkgs/tomlkit/package-version.txt index 35ad34429be..62d5dbdf3c7 100644 --- a/build/pkgs/tomlkit/package-version.txt +++ b/build/pkgs/tomlkit/package-version.txt @@ -1 +1 @@ -0.11.4 +0.11.5 From 783704656201cf6356a7c68eaeec64479326f7ec Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:43:04 -0700 Subject: [PATCH 295/414] build/pkgs/tinycss2: Update to 1.2.0 --- build/pkgs/tinycss2/checksums.ini | 6 +++--- build/pkgs/tinycss2/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tinycss2/checksums.ini b/build/pkgs/tinycss2/checksums.ini index 668d72fa9e4..cf10f3f8d3f 100644 --- a/build/pkgs/tinycss2/checksums.ini +++ b/build/pkgs/tinycss2/checksums.ini @@ -1,5 +1,5 @@ tarball=tinycss2-VERSION.tar.gz -sha1=4a05bce2f350b120791a59c6595564274ca4c6b3 -md5=60272f58f8d5834b2e09ffbc9bd5de53 -cksum=3520456675 +sha1=250a0e4d72241428b4172c0431b7a5d759ccb285 +md5=13b8548422f600032722c25a74706daa +cksum=2174916905 upstream_url=https://pypi.io/packages/source/t/tinycss2/tinycss2-VERSION.tar.gz diff --git a/build/pkgs/tinycss2/package-version.txt b/build/pkgs/tinycss2/package-version.txt index 524cb55242b..26aaba0e866 100644 --- a/build/pkgs/tinycss2/package-version.txt +++ b/build/pkgs/tinycss2/package-version.txt @@ -1 +1 @@ -1.1.1 +1.2.0 From e70c275d808218e7bb4627b85ebca5cffc98c320 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:43:14 -0700 Subject: [PATCH 296/414] build/pkgs/typing_extensions: Update to 4.4.0 --- build/pkgs/typing_extensions/checksums.ini | 6 +++--- build/pkgs/typing_extensions/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/typing_extensions/checksums.ini b/build/pkgs/typing_extensions/checksums.ini index 6a9672ec92f..d6cbf2607c0 100644 --- a/build/pkgs/typing_extensions/checksums.ini +++ b/build/pkgs/typing_extensions/checksums.ini @@ -1,5 +1,5 @@ tarball=typing_extensions-VERSION.tar.gz -sha1=efa40572330e9a3c38ba519028f36d7f93647a39 -md5=9b5b33ae64c94479fa0862cf8ae89d58 -cksum=1588180971 +sha1=9dbf798784009efaef80c8198a75b2a9e519eb95 +md5=5cfcb56ea6fc4972c3600c0030f4d136 +cksum=386983249 upstream_url=https://pypi.io/packages/source/t/typing_extensions/typing_extensions-VERSION.tar.gz diff --git a/build/pkgs/typing_extensions/package-version.txt b/build/pkgs/typing_extensions/package-version.txt index 37c18af77ed..fdc6698807a 100644 --- a/build/pkgs/typing_extensions/package-version.txt +++ b/build/pkgs/typing_extensions/package-version.txt @@ -1 +1 @@ -3.10.0.0 +4.4.0 From 84bf1dec0cc5742b4fcf92b09db535db5953d1a3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:43:25 -0700 Subject: [PATCH 297/414] build/pkgs/tzdata: Update to 2022.5 --- build/pkgs/tzdata/checksums.ini | 6 +++--- build/pkgs/tzdata/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tzdata/checksums.ini b/build/pkgs/tzdata/checksums.ini index 0f4b1dacf0e..bc975d6471a 100644 --- a/build/pkgs/tzdata/checksums.ini +++ b/build/pkgs/tzdata/checksums.ini @@ -1,5 +1,5 @@ tarball=tzdata-VERSION.tar.gz -sha1=b63835812a08e453376558df28e05bf30168e4c6 -md5=b458f1ceb9371d9d3051de2fd2c45bb8 -cksum=3348191929 +sha1=3acb57630d74572e9db51f91c5bdd38ef64ac100 +md5=23da690574817c8233185edeada3924c +cksum=467850857 upstream_url=https://pypi.io/packages/source/t/tzdata/tzdata-VERSION.tar.gz diff --git a/build/pkgs/tzdata/package-version.txt b/build/pkgs/tzdata/package-version.txt index 7eaecee1e5c..ef52d12ba35 100644 --- a/build/pkgs/tzdata/package-version.txt +++ b/build/pkgs/tzdata/package-version.txt @@ -1 +1 @@ -2022.1 +2022.5 From 6613a205120bae52c40eb8aab832502bf0433a65 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:43:37 -0700 Subject: [PATCH 298/414] build/pkgs/urllib3: Update to 1.26.12 --- build/pkgs/urllib3/checksums.ini | 6 +++--- build/pkgs/urllib3/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/urllib3/checksums.ini b/build/pkgs/urllib3/checksums.ini index 102eb61180b..bfb7c5afa48 100644 --- a/build/pkgs/urllib3/checksums.ini +++ b/build/pkgs/urllib3/checksums.ini @@ -1,5 +1,5 @@ tarball=urllib3-VERSION.tar.gz -sha1=f7fc28c04042d141272d9aa24ca9506b9e59f870 -md5=d4b58522821a33c5e421191b83e0dbac -cksum=1538549000 +sha1=ad6bd811a3f4c3e04d86c2706c9994c3e2236e53 +md5=ba308b52b9092184cf4905bc59a88fc0 +cksum=2776794349 upstream_url=https://pypi.io/packages/source/u/urllib3/urllib3-VERSION.tar.gz diff --git a/build/pkgs/urllib3/package-version.txt b/build/pkgs/urllib3/package-version.txt index 3cd33cde0f7..b74a856da82 100644 --- a/build/pkgs/urllib3/package-version.txt +++ b/build/pkgs/urllib3/package-version.txt @@ -1 +1 @@ -1.26.9 +1.26.12 From bb6351f85221e8d9b18bd48949b45e7630af598b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:44:02 -0700 Subject: [PATCH 299/414] build/pkgs/virtualenv: Update to 20.16.5 --- build/pkgs/virtualenv/checksums.ini | 6 +++--- build/pkgs/virtualenv/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/virtualenv/checksums.ini b/build/pkgs/virtualenv/checksums.ini index 5e1728927f4..2d9e0f511fe 100644 --- a/build/pkgs/virtualenv/checksums.ini +++ b/build/pkgs/virtualenv/checksums.ini @@ -1,5 +1,5 @@ tarball=virtualenv-VERSION.tar.gz -sha1=3c00a243652c2bdbf700828b2bca7733941e1453 -md5=1f7ddaff06a58ee4508bed1f03a5ce1c -cksum=2270354493 +sha1=24745f6095d59917577890172fd52feb8851f547 +md5=44c7d81666301c6c9d0b03a6751d9604 +cksum=1850303439 upstream_url=https://pypi.io/packages/source/v/virtualenv/virtualenv-VERSION.tar.gz diff --git a/build/pkgs/virtualenv/package-version.txt b/build/pkgs/virtualenv/package-version.txt index 418ef16ce2b..140bca73f83 100644 --- a/build/pkgs/virtualenv/package-version.txt +++ b/build/pkgs/virtualenv/package-version.txt @@ -1 +1 @@ -20.14.1 +20.16.5 From 4b389bc295bd1655a7df6b2acdee0f217b6f3465 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:44:13 -0700 Subject: [PATCH 300/414] build/pkgs/tox: Update to 3.26.0 --- build/pkgs/tox/checksums.ini | 6 +++--- build/pkgs/tox/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tox/checksums.ini b/build/pkgs/tox/checksums.ini index f64c6e12b24..af645e47c4b 100644 --- a/build/pkgs/tox/checksums.ini +++ b/build/pkgs/tox/checksums.ini @@ -1,5 +1,5 @@ tarball=tox-VERSION.tar.gz -sha1=b755ebd6a4fe57bb64bd9cc460761ac38d2c1bfd -md5=b270c0b956305570fb9a638ab72fecec -cksum=3980705014 +sha1=70341b4bd57be86410f352c7b30a07cefef087c3 +md5=7e261bc4050a698adf06dfb553869b58 +cksum=3295576412 upstream_url=https://pypi.io/packages/source/t/tox/tox-VERSION.tar.gz diff --git a/build/pkgs/tox/package-version.txt b/build/pkgs/tox/package-version.txt index 693bd59e3e6..419ede3b9cb 100644 --- a/build/pkgs/tox/package-version.txt +++ b/build/pkgs/tox/package-version.txt @@ -1 +1 @@ -3.24.3 +3.26.0 From 826d419baecbd3061614b5eadcf862125c3b5cdd Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:44:25 -0700 Subject: [PATCH 301/414] build/pkgs/zipp: Update to 3.9.0 --- build/pkgs/zipp/checksums.ini | 6 +++--- build/pkgs/zipp/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/zipp/checksums.ini b/build/pkgs/zipp/checksums.ini index 3c82ab5cc17..8a729185177 100644 --- a/build/pkgs/zipp/checksums.ini +++ b/build/pkgs/zipp/checksums.ini @@ -1,5 +1,5 @@ tarball=zipp-VERSION.tar.gz -sha1=52f43da4467b178ac97ca78a2487ecbc94b4a9b2 -md5=8864ff5ed01cd28755cc87f1443dbc67 -cksum=3776620702 +sha1=8ce743d9a8850db38b52c318087dbe4a2a9868d3 +md5=46815096f7e8cf91de9a0d6c84727608 +cksum=616631595 upstream_url=https://pypi.io/packages/source/z/zipp/zipp-VERSION.tar.gz diff --git a/build/pkgs/zipp/package-version.txt b/build/pkgs/zipp/package-version.txt index 19811903a7f..a5c4c763394 100644 --- a/build/pkgs/zipp/package-version.txt +++ b/build/pkgs/zipp/package-version.txt @@ -1 +1 @@ -3.8.0 +3.9.0 From 38ba4c8032af9f0fb7eae5b26aa10d3a141df2f8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:44:54 -0700 Subject: [PATCH 302/414] build/pkgs/asttokens: Update to 2.0.8 --- build/pkgs/asttokens/checksums.ini | 6 +++--- build/pkgs/asttokens/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/asttokens/checksums.ini b/build/pkgs/asttokens/checksums.ini index 2ebd46ce567..73c747fbc73 100644 --- a/build/pkgs/asttokens/checksums.ini +++ b/build/pkgs/asttokens/checksums.ini @@ -1,5 +1,5 @@ tarball=asttokens-VERSION.tar.gz -sha1=8a9c1fc8752fedb52189441f9f874f5e1afd5866 -md5=0a2a057b9c9a220bffdb3e7512062f17 -cksum=2612374104 +sha1=a8555556ffc39df85963ac3ee82bb8af063296c9 +md5=ba3cb54e6a851636df293b8a4253f80f +cksum=3689538391 upstream_url=https://pypi.io/packages/source/a/asttokens/asttokens-VERSION.tar.gz diff --git a/build/pkgs/asttokens/package-version.txt b/build/pkgs/asttokens/package-version.txt index e01025862f7..815e68dd20e 100644 --- a/build/pkgs/asttokens/package-version.txt +++ b/build/pkgs/asttokens/package-version.txt @@ -1 +1 @@ -2.0.5 +2.0.8 From df5430bc0a84da05179832d535765674e3f00f91 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:45:25 -0700 Subject: [PATCH 303/414] build/pkgs/bleach: Update to 5.0.1 --- build/pkgs/bleach/checksums.ini | 6 +++--- build/pkgs/bleach/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/bleach/checksums.ini b/build/pkgs/bleach/checksums.ini index 1eb2d84effa..4d4855aab72 100644 --- a/build/pkgs/bleach/checksums.ini +++ b/build/pkgs/bleach/checksums.ini @@ -1,5 +1,5 @@ tarball=bleach-VERSION.tar.gz -sha1=8b4652eb5a4a1cd6dbf35905a25f389da512f940 -md5=97322e672e4b285e6354c40d07166fc4 -cksum=1632919602 +sha1=73c6b8fad993b318859ca65c365ac2191edd35fc +md5=03b5faa43c0d771a86a2c4cb2575d070 +cksum=4204308806 upstream_url=https://pypi.io/packages/source/b/bleach/bleach-VERSION.tar.gz diff --git a/build/pkgs/bleach/package-version.txt b/build/pkgs/bleach/package-version.txt index 0062ac97180..6b244dcd696 100644 --- a/build/pkgs/bleach/package-version.txt +++ b/build/pkgs/bleach/package-version.txt @@ -1 +1 @@ -5.0.0 +5.0.1 From b588629e7d14300b8e5d0208ee320d27b4f936f0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:45:35 -0700 Subject: [PATCH 304/414] build/pkgs/cffi: Update to 1.15.1 --- build/pkgs/cffi/checksums.ini | 6 +++--- build/pkgs/cffi/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/cffi/checksums.ini b/build/pkgs/cffi/checksums.ini index 9d2863a8496..5e9ebc003f4 100644 --- a/build/pkgs/cffi/checksums.ini +++ b/build/pkgs/cffi/checksums.ini @@ -1,5 +1,5 @@ tarball=cffi-VERSION.tar.gz -sha1=9c51c29e35510adf7f94542e1f8e05611930b07b -md5=f3a3f26cd3335fc597479c9475da0a0b -cksum=3482630007 +sha1=c42a46cd11f6153f299cf10e9c236e8b2a143c21 +md5=f493860a6e98cd0c4178149568a6b4f6 +cksum=585894851 upstream_url=https://pypi.io/packages/source/c/cffi/cffi-VERSION.tar.gz diff --git a/build/pkgs/cffi/package-version.txt b/build/pkgs/cffi/package-version.txt index 141f2e805be..ace44233b4a 100644 --- a/build/pkgs/cffi/package-version.txt +++ b/build/pkgs/cffi/package-version.txt @@ -1 +1 @@ -1.15.0 +1.15.1 From a099c0389c979f553782bd875e2dcc7312b7f9c0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:45:52 -0700 Subject: [PATCH 305/414] build/pkgs/debugpy: Update to 1.6.3 --- build/pkgs/debugpy/checksums.ini | 6 +++--- build/pkgs/debugpy/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/debugpy/checksums.ini b/build/pkgs/debugpy/checksums.ini index 5872243e9f5..94e60c8de63 100644 --- a/build/pkgs/debugpy/checksums.ini +++ b/build/pkgs/debugpy/checksums.ini @@ -1,5 +1,5 @@ tarball=debugpy-VERSION.zip -sha1=5a0066e4641659c63ecc8d6ce35e96a2fd89b195 -md5=27a4789bfda161dc7de6a6860eeeff38 -cksum=708733483 +sha1=44ae7bfe2d355990604f83ee4c24eb81631b4433 +md5=a999f81d29db030bfacab544d5fb0976 +cksum=3944616380 upstream_url=https://pypi.io/packages/source/d/debugpy/debugpy-VERSION.zip diff --git a/build/pkgs/debugpy/package-version.txt b/build/pkgs/debugpy/package-version.txt index dc1e644a101..266146b87cb 100644 --- a/build/pkgs/debugpy/package-version.txt +++ b/build/pkgs/debugpy/package-version.txt @@ -1 +1 @@ -1.6.0 +1.6.3 From 8f2142f043a644fb384d5bb6b1c9c35bb061a5c3 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:46:01 -0700 Subject: [PATCH 306/414] build/pkgs/executing: Update to 1.1.1 --- build/pkgs/executing/checksums.ini | 6 +++--- build/pkgs/executing/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/executing/checksums.ini b/build/pkgs/executing/checksums.ini index 660ea8654b9..bc94cbcea54 100644 --- a/build/pkgs/executing/checksums.ini +++ b/build/pkgs/executing/checksums.ini @@ -1,5 +1,5 @@ tarball=executing-VERSION.tar.gz -sha1=9588832c2abca704a0a287d2c43690c046424697 -md5=43da806fc75eaba315e4947b329d2a90 -cksum=2140182428 +sha1=e962c1719fe8e259b8a7988421e5995ca0a4b8d3 +md5=5b4e105769218bc64eb0df434cb1cedf +cksum=3777168231 upstream_url=https://pypi.io/packages/source/e/executing/executing-VERSION.tar.gz diff --git a/build/pkgs/executing/package-version.txt b/build/pkgs/executing/package-version.txt index ee94dd834b5..524cb55242b 100644 --- a/build/pkgs/executing/package-version.txt +++ b/build/pkgs/executing/package-version.txt @@ -1 +1 @@ -0.8.3 +1.1.1 From 701db6ef42ef6b17577cdc69c751f608ba43ebed Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 17 Oct 2022 23:46:11 -0700 Subject: [PATCH 307/414] build/pkgs/fastjsonschema: Update to 2.16.2 --- build/pkgs/fastjsonschema/checksums.ini | 6 +++--- build/pkgs/fastjsonschema/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/fastjsonschema/checksums.ini b/build/pkgs/fastjsonschema/checksums.ini index 94301089b1a..9b6be281643 100644 --- a/build/pkgs/fastjsonschema/checksums.ini +++ b/build/pkgs/fastjsonschema/checksums.ini @@ -1,5 +1,5 @@ tarball=fastjsonschema-VERSION.tar.gz -sha1=3634374e5004103a3789753f0c145bb798f90874 -md5=c371e5315f66bdd18b62e14c66f89543 -cksum=2483060937 +sha1=a6c53c1eed4f0fbb9c7eaf0fc21fc2c0be85bcd8 +md5=d7d76db7518e64b53a13a7a2315a1671 +cksum=1205433737 upstream_url=https://pypi.io/packages/source/f/fastjsonschema/fastjsonschema-VERSION.tar.gz diff --git a/build/pkgs/fastjsonschema/package-version.txt b/build/pkgs/fastjsonschema/package-version.txt index 6480dd5ed87..43c85e79255 100644 --- a/build/pkgs/fastjsonschema/package-version.txt +++ b/build/pkgs/fastjsonschema/package-version.txt @@ -1 +1 @@ -2.15.3 +2.16.2 From 3077298f9b13bfbb905adb7b823e94d5001ebb14 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 18 Oct 2022 00:26:44 -0700 Subject: [PATCH 308/414] build/pkgs/setuptools_scm: Update to 6.4.2 --- build/pkgs/setuptools_scm/checksums.ini | 6 +++--- build/pkgs/setuptools_scm/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/setuptools_scm/checksums.ini b/build/pkgs/setuptools_scm/checksums.ini index a5c492c3e2f..a6f5ff1395b 100644 --- a/build/pkgs/setuptools_scm/checksums.ini +++ b/build/pkgs/setuptools_scm/checksums.ini @@ -1,5 +1,5 @@ tarball=setuptools_scm-VERSION.tar.gz -sha1=a4f02fddae697614e356cadfddb6241cc7737f38 -md5=32918d8ac566360c21411e0b3556c695 -cksum=1450556136 +sha1=0f34eba670121f9c41939ca8d805687de359f71c +md5=b4e02bf8e62ed49142ea7b42a68671d7 +cksum=1975160274 upstream_url=https://pypi.io/packages/source/s/setuptools_scm/setuptools_scm-VERSION.tar.gz diff --git a/build/pkgs/setuptools_scm/package-version.txt b/build/pkgs/setuptools_scm/package-version.txt index 91e4a9f2622..a4c853ea2ea 100644 --- a/build/pkgs/setuptools_scm/package-version.txt +++ b/build/pkgs/setuptools_scm/package-version.txt @@ -1 +1 @@ -6.3.2 +6.4.2 From 045e2282c5f3f8b1fb35a35d4195fa52cea0c480 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 18 Oct 2022 00:31:05 -0700 Subject: [PATCH 309/414] build/pkgs/platformdirs/dependencies: Update --- build/pkgs/hatch_vcs/SPKG.rst | 16 ++++++++++++++++ build/pkgs/hatch_vcs/checksums.ini | 5 +++++ build/pkgs/hatch_vcs/dependencies | 4 ++++ build/pkgs/hatch_vcs/install-requires.txt | 1 + build/pkgs/hatch_vcs/package-version.txt | 1 + build/pkgs/hatch_vcs/spkg-install.in | 2 ++ build/pkgs/hatch_vcs/type | 1 + build/pkgs/platformdirs/dependencies | 2 +- 8 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/hatch_vcs/SPKG.rst create mode 100644 build/pkgs/hatch_vcs/checksums.ini create mode 100644 build/pkgs/hatch_vcs/dependencies create mode 100644 build/pkgs/hatch_vcs/install-requires.txt create mode 100644 build/pkgs/hatch_vcs/package-version.txt create mode 100644 build/pkgs/hatch_vcs/spkg-install.in create mode 100644 build/pkgs/hatch_vcs/type diff --git a/build/pkgs/hatch_vcs/SPKG.rst b/build/pkgs/hatch_vcs/SPKG.rst new file mode 100644 index 00000000000..51f4780749e --- /dev/null +++ b/build/pkgs/hatch_vcs/SPKG.rst @@ -0,0 +1,16 @@ +hatch_vcs: Hatch plugin for versioning with your preferred VCS +============================================================== + +Description +----------- + +Hatch plugin for versioning with your preferred VCS + +License +------- + +Upstream Contact +---------------- + +https://pypi.org/project/hatch-vcs/ + diff --git a/build/pkgs/hatch_vcs/checksums.ini b/build/pkgs/hatch_vcs/checksums.ini new file mode 100644 index 00000000000..47e0c350f03 --- /dev/null +++ b/build/pkgs/hatch_vcs/checksums.ini @@ -0,0 +1,5 @@ +tarball=hatch_vcs-VERSION.tar.gz +sha1=9d38f55610b156b513d3d2a79f81cbf4fdea3cb2 +md5=e56b6d0c05cfb9b59d493c67f94d6e48 +cksum=680867691 +upstream_url=https://pypi.io/packages/source/h/hatch_vcs/hatch_vcs-VERSION.tar.gz diff --git a/build/pkgs/hatch_vcs/dependencies b/build/pkgs/hatch_vcs/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/hatch_vcs/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/hatch_vcs/install-requires.txt b/build/pkgs/hatch_vcs/install-requires.txt new file mode 100644 index 00000000000..04e2069fbb3 --- /dev/null +++ b/build/pkgs/hatch_vcs/install-requires.txt @@ -0,0 +1 @@ +hatch-vcs diff --git a/build/pkgs/hatch_vcs/package-version.txt b/build/pkgs/hatch_vcs/package-version.txt new file mode 100644 index 00000000000..0ea3a944b39 --- /dev/null +++ b/build/pkgs/hatch_vcs/package-version.txt @@ -0,0 +1 @@ +0.2.0 diff --git a/build/pkgs/hatch_vcs/spkg-install.in b/build/pkgs/hatch_vcs/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/hatch_vcs/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/hatch_vcs/type b/build/pkgs/hatch_vcs/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/hatch_vcs/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/platformdirs/dependencies b/build/pkgs/platformdirs/dependencies index 0738c2d7777..5b4aec583a4 100644 --- a/build/pkgs/platformdirs/dependencies +++ b/build/pkgs/platformdirs/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) +$(PYTHON) setuptools_scm | $(PYTHON_TOOLCHAIN) hatchling hatch_vcs ---------- All lines of this file are ignored except the first. From 8ecf31d16c95420094a572f6276958213170703b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 18 Oct 2022 12:36:44 -0700 Subject: [PATCH 310/414] build/pkgs/hatchling: Update to 1.11.0 --- build/pkgs/hatchling/checksums.ini | 6 +++--- build/pkgs/hatchling/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/hatchling/checksums.ini b/build/pkgs/hatchling/checksums.ini index 598cd79a2c7..cfa9e772f32 100644 --- a/build/pkgs/hatchling/checksums.ini +++ b/build/pkgs/hatchling/checksums.ini @@ -1,5 +1,5 @@ tarball=hatchling-VERSION.tar.gz -sha1=8f102796a225fb18b0571a44308341c7211d5d94 -md5=c50eff4f711cee451037ec7eb780154a -cksum=3180958969 +sha1=e5f87d37fe2a2a20aff992ad5bdc710b4233059d +md5=9bd7e55ca208af96a1fcfd192ce54eec +cksum=3049487864 upstream_url=https://pypi.io/packages/source/h/hatchling/hatchling-VERSION.tar.gz diff --git a/build/pkgs/hatchling/package-version.txt b/build/pkgs/hatchling/package-version.txt index 81c871de46b..1cac385c6cb 100644 --- a/build/pkgs/hatchling/package-version.txt +++ b/build/pkgs/hatchling/package-version.txt @@ -1 +1 @@ -1.10.0 +1.11.0 From 5fc21762242d41875be7e733dc2dd93b64a0cfc8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:41:13 -0700 Subject: [PATCH 311/414] build/pkgs/asttokens: Update to 2.1.0 --- build/pkgs/asttokens/checksums.ini | 6 +++--- build/pkgs/asttokens/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/asttokens/checksums.ini b/build/pkgs/asttokens/checksums.ini index 73c747fbc73..e69f1ecdfb3 100644 --- a/build/pkgs/asttokens/checksums.ini +++ b/build/pkgs/asttokens/checksums.ini @@ -1,5 +1,5 @@ tarball=asttokens-VERSION.tar.gz -sha1=a8555556ffc39df85963ac3ee82bb8af063296c9 -md5=ba3cb54e6a851636df293b8a4253f80f -cksum=3689538391 +sha1=cca6058c6c23195148be93bfa32c0a0ca9b2f873 +md5=67b269e359fcb404cd8626985f3676ae +cksum=3749309047 upstream_url=https://pypi.io/packages/source/a/asttokens/asttokens-VERSION.tar.gz diff --git a/build/pkgs/asttokens/package-version.txt b/build/pkgs/asttokens/package-version.txt index 815e68dd20e..7ec1d6db408 100644 --- a/build/pkgs/asttokens/package-version.txt +++ b/build/pkgs/asttokens/package-version.txt @@ -1 +1 @@ -2.0.8 +2.1.0 From 7d876d9228b7c816866f4953a8b83e8014cd91ee Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:43:02 -0700 Subject: [PATCH 312/414] build/pkgs/hatchling: Update to 1.11.1 --- build/pkgs/hatchling/checksums.ini | 6 +++--- build/pkgs/hatchling/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/hatchling/checksums.ini b/build/pkgs/hatchling/checksums.ini index cfa9e772f32..2980620c8d8 100644 --- a/build/pkgs/hatchling/checksums.ini +++ b/build/pkgs/hatchling/checksums.ini @@ -1,5 +1,5 @@ tarball=hatchling-VERSION.tar.gz -sha1=e5f87d37fe2a2a20aff992ad5bdc710b4233059d -md5=9bd7e55ca208af96a1fcfd192ce54eec -cksum=3049487864 +sha1=5d2e7ac6feffa2dfdbf81e7d10661ac1b08b9608 +md5=e06cc65ac646f9b01df5406aa1f97022 +cksum=310056602 upstream_url=https://pypi.io/packages/source/h/hatchling/hatchling-VERSION.tar.gz diff --git a/build/pkgs/hatchling/package-version.txt b/build/pkgs/hatchling/package-version.txt index 1cac385c6cb..720c7384c61 100644 --- a/build/pkgs/hatchling/package-version.txt +++ b/build/pkgs/hatchling/package-version.txt @@ -1 +1 @@ -1.11.0 +1.11.1 From eb4ef6a0e9b9988377e95791ba32022ca6e1a4d5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:44:01 -0700 Subject: [PATCH 313/414] build/pkgs/pytz: Update to 2022.5 --- build/pkgs/pytz/checksums.ini | 6 +++--- build/pkgs/pytz/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pytz/checksums.ini b/build/pkgs/pytz/checksums.ini index 0d031b316d0..9c3074def07 100644 --- a/build/pkgs/pytz/checksums.ini +++ b/build/pkgs/pytz/checksums.ini @@ -1,5 +1,5 @@ tarball=pytz-VERSION.tar.gz -sha1=74975d8618b7c71bfc38958979ab96da4d9a3aba -md5=b1d2ed6592bbdf6002ef52b4ab8e2efe -cksum=1769835625 +sha1=b356ab5a8b326e9857bbce3e7a1799fc56844827 +md5=91747f483e2906cddda91b0df0b01254 +cksum=635792532 upstream_url=https://pypi.io/packages/source/p/pytz/pytz-VERSION.tar.gz diff --git a/build/pkgs/pytz/package-version.txt b/build/pkgs/pytz/package-version.txt index a2a5548bc1c..ef52d12ba35 100644 --- a/build/pkgs/pytz/package-version.txt +++ b/build/pkgs/pytz/package-version.txt @@ -1 +1 @@ -2022.4 +2022.5 From 2385e729c6d5cc8fed860ccda2ef7aa6119849d7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:44:31 -0700 Subject: [PATCH 314/414] build/pkgs/tinycss2: Update to 1.2.1 --- build/pkgs/tinycss2/checksums.ini | 6 +++--- build/pkgs/tinycss2/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tinycss2/checksums.ini b/build/pkgs/tinycss2/checksums.ini index cf10f3f8d3f..c905e293396 100644 --- a/build/pkgs/tinycss2/checksums.ini +++ b/build/pkgs/tinycss2/checksums.ini @@ -1,5 +1,5 @@ tarball=tinycss2-VERSION.tar.gz -sha1=250a0e4d72241428b4172c0431b7a5d759ccb285 -md5=13b8548422f600032722c25a74706daa -cksum=2174916905 +sha1=3871ffec30bde346d1a17f80a423dce488bad4f7 +md5=e8a06102e7f42ca791463f11ce7b814d +cksum=1840765267 upstream_url=https://pypi.io/packages/source/t/tinycss2/tinycss2-VERSION.tar.gz diff --git a/build/pkgs/tinycss2/package-version.txt b/build/pkgs/tinycss2/package-version.txt index 26aaba0e866..6085e946503 100644 --- a/build/pkgs/tinycss2/package-version.txt +++ b/build/pkgs/tinycss2/package-version.txt @@ -1 +1 @@ -1.2.0 +1.2.1 From 881ed1cb9738b706b0bc50560d5e8f65b4b9a28d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:44:45 -0700 Subject: [PATCH 315/414] build/pkgs/tomlkit: Update to 0.11.6 --- build/pkgs/tomlkit/checksums.ini | 6 +++--- build/pkgs/tomlkit/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tomlkit/checksums.ini b/build/pkgs/tomlkit/checksums.ini index c5c309c6b4b..545bb2bc02b 100644 --- a/build/pkgs/tomlkit/checksums.ini +++ b/build/pkgs/tomlkit/checksums.ini @@ -1,5 +1,5 @@ tarball=tomlkit-VERSION.tar.gz -sha1=3d03c83e2dc36d2b1cef1bb82902f89ce639e2fe -md5=d5702dd3ecf513935d24d673761f5296 -cksum=3072168699 +sha1=b097f71385b3b693a41a23ecd551959faae73e0d +md5=ac33a015aa5f3f8e8e0667081b388bb7 +cksum=785586052 upstream_url=https://pypi.io/packages/source/t/tomlkit/tomlkit-VERSION.tar.gz diff --git a/build/pkgs/tomlkit/package-version.txt b/build/pkgs/tomlkit/package-version.txt index 62d5dbdf3c7..e5cbde33e62 100644 --- a/build/pkgs/tomlkit/package-version.txt +++ b/build/pkgs/tomlkit/package-version.txt @@ -1 +1 @@ -0.11.5 +0.11.6 From a0e053d39f57bb4f069f6185997f0d5268e9989b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:44:57 -0700 Subject: [PATCH 316/414] build/pkgs/tox: Update to 3.27.0 --- build/pkgs/tox/checksums.ini | 6 +++--- build/pkgs/tox/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tox/checksums.ini b/build/pkgs/tox/checksums.ini index af645e47c4b..19a159a8e4a 100644 --- a/build/pkgs/tox/checksums.ini +++ b/build/pkgs/tox/checksums.ini @@ -1,5 +1,5 @@ tarball=tox-VERSION.tar.gz -sha1=70341b4bd57be86410f352c7b30a07cefef087c3 -md5=7e261bc4050a698adf06dfb553869b58 -cksum=3295576412 +sha1=4a17b94eea345a2fb1a76106fb4d01ac9aca3569 +md5=ed4a11d13cd6a206b516c84750109602 +cksum=3144404727 upstream_url=https://pypi.io/packages/source/t/tox/tox-VERSION.tar.gz diff --git a/build/pkgs/tox/package-version.txt b/build/pkgs/tox/package-version.txt index 419ede3b9cb..8c53120442c 100644 --- a/build/pkgs/tox/package-version.txt +++ b/build/pkgs/tox/package-version.txt @@ -1 +1 @@ -3.26.0 +3.27.0 From 88556b190d8b301506b74e78154e1e8520adbed9 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:45:14 -0700 Subject: [PATCH 317/414] build/pkgs/tzdata: Update to 2022.6 --- build/pkgs/tzdata/checksums.ini | 6 +++--- build/pkgs/tzdata/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tzdata/checksums.ini b/build/pkgs/tzdata/checksums.ini index bc975d6471a..86bf2db8de4 100644 --- a/build/pkgs/tzdata/checksums.ini +++ b/build/pkgs/tzdata/checksums.ini @@ -1,5 +1,5 @@ tarball=tzdata-VERSION.tar.gz -sha1=3acb57630d74572e9db51f91c5bdd38ef64ac100 -md5=23da690574817c8233185edeada3924c -cksum=467850857 +sha1=e244bf1bde63515d3f8a452d3bbe9f97738e1e70 +md5=2ad4652fecc6ef4f6794726d3f367363 +cksum=2894139369 upstream_url=https://pypi.io/packages/source/t/tzdata/tzdata-VERSION.tar.gz diff --git a/build/pkgs/tzdata/package-version.txt b/build/pkgs/tzdata/package-version.txt index ef52d12ba35..abd454cf321 100644 --- a/build/pkgs/tzdata/package-version.txt +++ b/build/pkgs/tzdata/package-version.txt @@ -1 +1 @@ -2022.5 +2022.6 From 495c138b2ac8f2c46dafe1bce8375999d38b2a8c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:45:35 -0700 Subject: [PATCH 318/414] build/pkgs/virtualenv: Update to 20.16.6 --- build/pkgs/virtualenv/checksums.ini | 6 +++--- build/pkgs/virtualenv/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/virtualenv/checksums.ini b/build/pkgs/virtualenv/checksums.ini index 2d9e0f511fe..e324ed781ee 100644 --- a/build/pkgs/virtualenv/checksums.ini +++ b/build/pkgs/virtualenv/checksums.ini @@ -1,5 +1,5 @@ tarball=virtualenv-VERSION.tar.gz -sha1=24745f6095d59917577890172fd52feb8851f547 -md5=44c7d81666301c6c9d0b03a6751d9604 -cksum=1850303439 +sha1=8371dccb9866b40c3fdc5c0aa9c8f034cc0b174b +md5=b2d60f3c431f370b5fed5169b94f4798 +cksum=3124829245 upstream_url=https://pypi.io/packages/source/v/virtualenv/virtualenv-VERSION.tar.gz diff --git a/build/pkgs/virtualenv/package-version.txt b/build/pkgs/virtualenv/package-version.txt index 140bca73f83..d1df974a56b 100644 --- a/build/pkgs/virtualenv/package-version.txt +++ b/build/pkgs/virtualenv/package-version.txt @@ -1 +1 @@ -20.16.5 +20.16.6 From 99b938824441aee06465e709387e591244fda46a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:45:44 -0700 Subject: [PATCH 319/414] build/pkgs/zipp: Update to 3.10.0 --- build/pkgs/zipp/checksums.ini | 6 +++--- build/pkgs/zipp/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/zipp/checksums.ini b/build/pkgs/zipp/checksums.ini index 8a729185177..5876b93b924 100644 --- a/build/pkgs/zipp/checksums.ini +++ b/build/pkgs/zipp/checksums.ini @@ -1,5 +1,5 @@ tarball=zipp-VERSION.tar.gz -sha1=8ce743d9a8850db38b52c318087dbe4a2a9868d3 -md5=46815096f7e8cf91de9a0d6c84727608 -cksum=616631595 +sha1=017268ef95e7da55ca11e695b63cda797d7a64be +md5=f75b65d022528a44877626641f0f95c3 +cksum=1239244009 upstream_url=https://pypi.io/packages/source/z/zipp/zipp-VERSION.tar.gz diff --git a/build/pkgs/zipp/package-version.txt b/build/pkgs/zipp/package-version.txt index a5c4c763394..30291cba223 100644 --- a/build/pkgs/zipp/package-version.txt +++ b/build/pkgs/zipp/package-version.txt @@ -1 +1 @@ -3.9.0 +3.10.0 From 2affa9db5b8ab66c1ff52c96182b3842a08b4e5a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:46:20 -0700 Subject: [PATCH 320/414] build/pkgs/stack_data: Update to 0.6.0 --- build/pkgs/stack_data/checksums.ini | 6 +++--- build/pkgs/stack_data/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/stack_data/checksums.ini b/build/pkgs/stack_data/checksums.ini index 7058412912e..13d92e56bd8 100644 --- a/build/pkgs/stack_data/checksums.ini +++ b/build/pkgs/stack_data/checksums.ini @@ -1,5 +1,5 @@ tarball=stack_data-VERSION.tar.gz -sha1=280dc05517f29dd0d450679304a9bb6a0fa41a25 -md5=d39afb043bdb116b8d568c2a82f32227 -cksum=3446203117 +sha1=5e69d397ae31e6dcd995765d4ab51cb6b897fb11 +md5=eedee8944e6e08ddb3195f968553e861 +cksum=64651866 upstream_url=https://pypi.io/packages/source/s/stack_data/stack_data-VERSION.tar.gz diff --git a/build/pkgs/stack_data/package-version.txt b/build/pkgs/stack_data/package-version.txt index 0d91a54c7d4..a918a2aa18d 100644 --- a/build/pkgs/stack_data/package-version.txt +++ b/build/pkgs/stack_data/package-version.txt @@ -1 +1 @@ -0.3.0 +0.6.0 From 14344db952b4f0a66f46b25dbc9eca3f91b35587 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 14:51:45 -0700 Subject: [PATCH 321/414] build/pkgs/jsonschema/dependencies: Add hatch_vcs --- build/pkgs/jsonschema/dependencies | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pkgs/jsonschema/dependencies b/build/pkgs/jsonschema/dependencies index e488e4e4422..51698156cf0 100644 --- a/build/pkgs/jsonschema/dependencies +++ b/build/pkgs/jsonschema/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) vcversioner attrs importlib_metadata pyrsistent | $(PYTHON_TOOLCHAIN) hatchling hatch_fancy_pypi_readme +$(PYTHON) vcversioner attrs importlib_metadata pyrsistent | $(PYTHON_TOOLCHAIN) hatchling hatch_vcs hatch_fancy_pypi_readme ---------- All lines of this file are ignored except the first. From 9604ef08f7403a1c9cfbd4d9659741dc23baa078 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 15:01:11 -0700 Subject: [PATCH 322/414] build/pkgs/executing: Update to 1.2.0 --- build/pkgs/executing/checksums.ini | 6 +++--- build/pkgs/executing/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/executing/checksums.ini b/build/pkgs/executing/checksums.ini index bc94cbcea54..71e107aafbe 100644 --- a/build/pkgs/executing/checksums.ini +++ b/build/pkgs/executing/checksums.ini @@ -1,5 +1,5 @@ tarball=executing-VERSION.tar.gz -sha1=e962c1719fe8e259b8a7988421e5995ca0a4b8d3 -md5=5b4e105769218bc64eb0df434cb1cedf -cksum=3777168231 +sha1=ac9b0cbedd1166bce7a3b9f8542f8d1fafdd8c73 +md5=e6fa9a6abf00555ccc8a6b3524729238 +cksum=1761713270 upstream_url=https://pypi.io/packages/source/e/executing/executing-VERSION.tar.gz diff --git a/build/pkgs/executing/package-version.txt b/build/pkgs/executing/package-version.txt index 524cb55242b..26aaba0e866 100644 --- a/build/pkgs/executing/package-version.txt +++ b/build/pkgs/executing/package-version.txt @@ -1 +1 @@ -1.1.1 +1.2.0 From 6a281622f4fcaaeb8c76231c5d9cb0a949ed4e0a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 30 Oct 2022 15:01:43 -0700 Subject: [PATCH 323/414] build/pkgs: Add missing dependencies --- build/pkgs/hatch_fancy_pypi_readme/dependencies | 2 +- build/pkgs/hatch_vcs/dependencies | 2 +- build/pkgs/ipykernel/dependencies | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/pkgs/hatch_fancy_pypi_readme/dependencies b/build/pkgs/hatch_fancy_pypi_readme/dependencies index 0738c2d7777..8cd44d06682 100644 --- a/build/pkgs/hatch_fancy_pypi_readme/dependencies +++ b/build/pkgs/hatch_fancy_pypi_readme/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) +$(PYTHON) | $(PYTHON_TOOLCHAIN) hatchling ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/hatch_vcs/dependencies b/build/pkgs/hatch_vcs/dependencies index 0738c2d7777..8cd44d06682 100644 --- a/build/pkgs/hatch_vcs/dependencies +++ b/build/pkgs/hatch_vcs/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) +$(PYTHON) | $(PYTHON_TOOLCHAIN) hatchling ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/ipykernel/dependencies b/build/pkgs/ipykernel/dependencies index d2677a8d27b..792c3e70634 100644 --- a/build/pkgs/ipykernel/dependencies +++ b/build/pkgs/ipykernel/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) ipython_genutils importlib_metadata matplotlib_inline ipython jupyter_client tornado appnope traitlets | $(PYTHON_TOOLCHAIN) +$(PYTHON) ipython_genutils importlib_metadata matplotlib_inline ipython jupyter_client tornado appnope traitlets executing | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. From 0d6e8035b30a012848579546257f2ad6d9521ac5 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 31 Oct 2022 08:46:35 +0900 Subject: [PATCH 324/414] Fixing failing test due to change. --- src/sage/algebras/fusion_rings/fusion_ring.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index a9bc2eed23a..80532a5988c 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -587,8 +587,18 @@ def root_of_unity(self, r, base_coercion=True): sage: A11 = FusionRing("A1", 1) sage: A11.field() Cyclotomic Field of order 24 and degree 8 - sage: [A11.root_of_unity(2/x) for x in [1..7]] - [1, -1, zeta24^4 - 1, zeta24^6, None, zeta24^4, None] + sage: for n in [1..7]: + ....: try: + ....: print(n, A11.root_of_unity(2/n)) + ....: except ValueError as err: + ....: print(n, err) + 1 1 + 2 -1 + 3 zeta24^4 - 1 + 4 zeta24^6 + 5 not an integer root of unity + 6 zeta24^4 + 7 not an integer root of unity """ n = 2 * r * self._cyclotomic_order if n not in ZZ: From 0be29f7a065db5eabe78d5ff11ff9391279fa013 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 31 Oct 2022 08:52:01 +0900 Subject: [PATCH 325/414] Changing to a more accurate error message. --- src/sage/algebras/fusion_rings/fusion_ring.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index 80532a5988c..87b356662b7 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -596,13 +596,13 @@ def root_of_unity(self, r, base_coercion=True): 2 -1 3 zeta24^4 - 1 4 zeta24^6 - 5 not an integer root of unity + 5 not a root of unity in the field 6 zeta24^4 - 7 not an integer root of unity + 7 not a root of unity in the field """ n = 2 * r * self._cyclotomic_order if n not in ZZ: - raise ValueError("not an integer root of unity") + raise ValueError("not a root of unity in the field") ret = self.field().gen() ** n if (not base_coercion) or (self._basecoer is None): return ret From 57825e92cd660d98ef4162a23cd20012b7a8884b Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 31 Oct 2022 15:13:24 +0900 Subject: [PATCH 326/414] Updating the documentation. --- src/sage/algebras/fusion_rings/fusion_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index 87b356662b7..d96afeaaee3 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -49,7 +49,7 @@ class FusionRing(WeylCharacterRing): The cyclotomic order is an integer `N` such that all computations will return elements of the cyclotomic field of `N`-th roots of unity. Normally you will never need to change this but consider changing it - if :meth:`root_of_unity` ever returns ``None``. + if :meth:`root_of_unity` raises a ``ValueError``. This algebra has a basis (sometimes called *primary fields* but here called *simple objects*) indexed by the weights of level `\leq k`. From 4d56288de56b4b90b5200c5a10f5536e87ce2c99 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 31 Oct 2022 17:13:05 +0900 Subject: [PATCH 327/414] Checking that the RHS is a scalar of 1, not the LHS. --- src/sage/algebras/clifford_algebra_element.pyx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sage/algebras/clifford_algebra_element.pyx b/src/sage/algebras/clifford_algebra_element.pyx index e065b6b70b9..00295967ae1 100644 --- a/src/sage/algebras/clifford_algebra_element.pyx +++ b/src/sage/algebras/clifford_algebra_element.pyx @@ -90,6 +90,15 @@ cdef class CliffordAlgebraElement(IndexedFreeModuleElement): 0 sage: 0*x 0 + + :trac:`34707`:: + + sage: Q = QuadraticForm(QQ, 2, [0,5,0]) + sage: C. = CliffordAlgebra(Q) + sage: (q * p) * q + 5*q + sage: q * (p * q) + 5*q """ Q = self._parent._quadratic_form zero = self._parent._base.zero() @@ -110,7 +119,7 @@ cdef class CliffordAlgebraElement(IndexedFreeModuleElement): if ml.isempty(): return rhs._mul_term_self(ml, cl) if len(rhs._monomial_coefficients) == 1: - mr, cr = next(iter(self._monomial_coefficients.items())) + mr, cr = next(iter(rhs._monomial_coefficients.items())) if mr.isempty(): return self._mul_self_term(mr, cr) @@ -125,7 +134,7 @@ cdef class CliffordAlgebraElement(IndexedFreeModuleElement): # the dictionary describing the element # ``e[i]`` * (the element described by the dictionary ``cur``) # (where ``e[i]`` is the ``i``-th standard basis vector). - for mr,cr in cur.items(): + for mr, cr in cur.items(): # Commute the factor as necessary until we are in order for j in mr: @@ -161,7 +170,7 @@ cdef class CliffordAlgebraElement(IndexedFreeModuleElement): cur = next_level # Add the distributed terms to the total - for index,coeff in cur.items(): + for index, coeff in cur.items(): d[index] = d.get(index, zero) + cl * coeff if d[index] == zero: del d[index] From 9c35f8def921539249331f6969b45dfc9365c2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 31 Oct 2022 15:05:31 +0100 Subject: [PATCH 328/414] add test for absolute disc of number fields --- src/sage/categories/number_fields.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/sage/categories/number_fields.py b/src/sage/categories/number_fields.py index 69fe01147f4..b54a38dbaa2 100644 --- a/src/sage/categories/number_fields.py +++ b/src/sage/categories/number_fields.py @@ -209,5 +209,28 @@ def zeta_function(self, prec=53, raise ValueError('algorithm must be "gp" or "pari"') + def _test_absolute_disc(self, **options): + r""" + Run basic tests for the method :meth:`absolute_discriminant` of ``self``. + + See the documentation for :class:`TestSuite` for information on + further options. + + INPUT: + + - ``options`` -- any keyword arguments accepted by :meth:`_tester` + + EXAMPLES: + + By default, this method tests only the elements returned by + ``self.some_elements()``:: + + sage: S = NumberField(x**3-x-1, 'a') + sage: S._test_absolute_disc() + """ + from sage.rings.integer import Integer + tester = self._tester(**options) + tester.assertIsInstance(self.absolute_discriminant(), Integer) + class ElementMethods: pass From 1f2bd704f13f617447d3a2b5dfe6ae849971543b Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Mon, 31 Oct 2022 15:35:53 +0000 Subject: [PATCH 329/414] Do not check for normalized matrix in hadmard_matrix --- src/sage/combinat/matrices/hadamard_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 0344c4ade32..f4911807d9e 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -462,7 +462,7 @@ def hadamard_matrix(n,existence=False, check=True): raise ValueError("The Hadamard matrix of order %s is not yet implemented." % n) if check: - assert is_hadamard_matrix(M, normalized=True) + assert is_hadamard_matrix(M) return M From 810fc34a9a6f7097a996e12a1258bbdf6dc68161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 31 Oct 2022 20:42:40 +0100 Subject: [PATCH 330/414] partial pep8 cleanup for number_field.py --- src/sage/rings/number_field/number_field.py | 491 ++++++++++---------- 1 file changed, 239 insertions(+), 252 deletions(-) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index a2f0e3d047c..ccafb7e20be 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -565,9 +565,9 @@ def NumberField(polynomial, name=None, check=True, names=None, embedding=None, latex_name = latex_names for key, val in kwds.items(): if key not in ['implementation', 'prec']: - raise TypeError("NumberField() got an unexpected keyword argument '%s'"%key) + raise TypeError("NumberField() got an unexpected keyword argument '%s'" % key) if not (val is None or isinstance(val, list) and all(c is None for c in val)): - raise NotImplementedError("Number field with prescribed %s is not implemented"%key) + raise NotImplementedError("Number field with prescribed %s is not implemented" % key) if isinstance(polynomial, (list,tuple)): return NumberFieldTower(polynomial, names=name, check=check, embeddings=embedding, latex_names=latex_name, assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes, structures=structure) @@ -843,7 +843,7 @@ def NumberFieldTower(polynomials, names, check=True, embeddings=None, latex_name except IndexError: names = normalize_names(1, names) if len(polynomials) > 1: - names = ['%s%s'%(names[0], i) for i in range(len(polynomials))] + names = ['%s%s' % (names[0], i) for i in range(len(polynomials))] if embeddings is None: embeddings = [None] * len(polynomials) @@ -869,7 +869,7 @@ def NumberFieldTower(polynomials, names, check=True, embeddings=None, latex_name var = f.variable_name() if is_Polynomial(f) else 'x' R = w[var] # polynomial ring - return w.extension(R(f), name, check=check, embedding=embeddings[0], structure=structures[0], latex_name=latex_names[0]) # currently, extension does not accept assume_disc_small, or maximize_at_primes + return w.extension(R(f), name, check=check, embedding=embeddings[0], structure=structures[0], latex_name=latex_names[0]) # currently, extension does not accept assume_disc_small, or maximize_at_primes def QuadraticField(D, name='a', check=True, embedding=True, latex_name='sqrt', **args): @@ -1042,7 +1042,7 @@ def is_AbsoluteNumberField(x): return isinstance(x, NumberField_absolute) -def is_QuadraticField(x): +def is_QuadraticField(x) -> bool: r""" Return True if x is of the quadratic *number* field type. @@ -1236,7 +1236,7 @@ def create_object(self, version, key, **extra_args): CyclotomicField = CyclotomicFieldFactory("sage.rings.number_field.number_field.CyclotomicField") -def is_CyclotomicField(x): +def is_CyclotomicField(x) -> bool: """ Return True if x is a cyclotomic field, i.e., of the special cyclotomic field class. This function does not return True for a @@ -1448,17 +1448,17 @@ def __init__(self, polynomial, name, latex_name, if category is None: category = default_category else: - assert category.is_subcategory(default_category), "%s is not a subcategory of %s"%(category, default_category) + assert category.is_subcategory(default_category), "%s is not a subcategory of %s" % (category, default_category) ParentWithGens.__init__(self, QQ, name, category=category) if not isinstance(polynomial, polynomial_element.Polynomial): - raise TypeError("polynomial (=%s) must be a polynomial"%repr(polynomial)) + raise TypeError("polynomial (=%s) must be a polynomial" % repr(polynomial)) if check: if not polynomial.parent().base_ring() == QQ: raise TypeError("polynomial must be defined over rational field") if not polynomial.is_irreducible(): - raise ValueError("defining polynomial (%s) must be irreducible"%polynomial) + raise ValueError("defining polynomial (%s) must be irreducible" % polynomial) self._assign_names(name) self._latex_names = (latex_name,) @@ -1584,12 +1584,12 @@ def _magma_init_(self, magma): """ # Get magma version of defining polynomial of this number field f = self._magma_polynomial_(magma) - s = 'NumberField(%s)'%f.name() + s = 'NumberField(%s)' % f.name() return magma._with_names(s, self.variable_names()) def construction(self): r""" - Construction of self + Construction of self. EXAMPLES:: @@ -1813,7 +1813,7 @@ def _element_constructor_(self, x, check=True): % (x, self.pari_polynomial())) beta = self._pari_absolute_structure()[2] x = x(beta).lift() - else: # constant polynomial + else: # constant polynomial x = x[0] else: raise TypeError("%s has unsupported PARI type %s" % (x, x.type())) @@ -1824,10 +1824,10 @@ def _element_constructor_(self, x, check=True): if self.variable_name() in s: return self._convert_from_str(s) return self._convert_from_str(s.replace('!', '')) - elif isinstance(x,str): + elif isinstance(x, str): return self._convert_from_str(x) elif (isinstance(x, (tuple, list)) or - isinstance(x, sage.modules.free_module_element.FreeModuleElement)): + isinstance(x, sage.modules.free_module_element.FreeModuleElement)): if len(x) != self.relative_degree(): raise ValueError("Length must be equal to the degree of this number field") base = self.base_ring() @@ -1901,9 +1901,9 @@ def _convert_non_number_field_element(self, x): return self._element_class(self, x) if isinstance(x, sage.rings.polynomial.polynomial_quotient_ring_element.PolynomialQuotientRingElement)\ - and (x in self.polynomial_quotient_ring()): + and (x in self.polynomial_quotient_ring()): y = self.polynomial_ring().gen() - return x.lift().subs({y:self.gen()}) + return x.lift().subs({y: self.gen()}) if isinstance(x, (sage.rings.qqbar.AlgebraicNumber, sage.rings.qqbar.AlgebraicReal)): return self._convert_from_qqbar(x) @@ -2371,7 +2371,7 @@ def change_generator(self, alpha, name=None, names=None): alpha = self(alpha) K, from_K = self.subfield(alpha, name=name) if K.degree() != self.degree(): - raise ValueError("alpha must generate a field of degree %s, but alpha generates a subfield of degree %s"%(self.degree(), K.degree())) + raise ValueError("alpha must generate a field of degree %s, but alpha generates a subfield of degree %s" % (self.degree(), K.degree())) # Now compute to_K, which is an isomorphism # from self to K such that from_K(to_K(x)) == x for all x, # and to_K(from_K(y)) == y. @@ -2613,7 +2613,7 @@ def quadratic_defect(self, a, p, check=True): raise TypeError(str(a) + " must be an element of " + str(self)) if not self == QQ and not p.parent() == self.ideal_monoid(): raise TypeError(str(p) + " is not a prime ideal in " - + str(self.ideal_monoid())) + + str(self.ideal_monoid())) if check and not p.is_prime(): raise ValueError(str(p) + " must be prime") if a.is_zero(): @@ -2646,7 +2646,7 @@ def quadratic_defect(self, a, p, check=True): s = self(q((a - 1) / pi**w)**(1/2)) a = a / (1 + s*(pi**(w/2)))**2 w = (a - 1).valuation(p) - if w < u and w % 2 ==1: + if w < u and w % 2: return v + w if w == u and (f + F((a-1) / 4)).is_irreducible(): return v + w @@ -2676,7 +2676,7 @@ def absolute_field(self, names): """ return NumberField(self.defining_polynomial(), names, check=False, structure=structure.NameChange(self)) - def is_isomorphic(self, other, isomorphism_maps = False): + def is_isomorphic(self, other, isomorphism_maps=False) -> bool: """ Return True if self is isomorphic as a number field to other. @@ -2850,16 +2850,14 @@ def is_CM(self): True sage: E.is_CM_extension() False - """ - - #Return cached answer if available + # Return cached answer if available try: return self.__is_CM except(AttributeError): pass - #Then, deal with simple cases + # Then, deal with simple cases if is_odd(self.absolute_degree()): self.__is_CM = False return False @@ -2885,8 +2883,8 @@ def is_CM(self): return True K = self.absolute_field('z') - #Check for index 2 subextensions that are totally real - possibilities = K.subfields(K.absolute_degree()/2) + # Check for index 2 subextensions that are totally real + possibilities = K.subfields(K.absolute_degree() / 2) for F, phi, _ in possibilities: if F.is_totally_real(): self.__is_CM = True @@ -2933,16 +2931,14 @@ def complex_conjugation(self): -a sage: cc(b) -b - """ - - #Return cached answer if available + # Return cached answer if available try: return self.__complex_conjugation except(AttributeError): pass - #Then, deal with simple cases + # Then, deal with simple cases if isinstance( self, sage.rings.number_field.number_field.NumberField_quadratic): disc = self.discriminant() @@ -2967,7 +2963,7 @@ def complex_conjugation(self): if not self.is_CM(): raise ValueError('Complex conjugation is only well-defined for fields contained in CM fields.') - #In the remaining case, self.is_CM() should have cached __max_tot_real_sub + # In the remaining case, self.is_CM() should have cached __max_tot_real_sub try: F, phi = self.__max_tot_real_sub except(AttributeError): @@ -2975,8 +2971,8 @@ def complex_conjugation(self): if self.is_absolute(): K_rel = self.relativize(phi, self.variable_name() * 2) to_abs, from_abs = K_rel.structure() - self.__complex_conjugation = K_rel.automorphisms()[1].pre_compose( \ - from_abs).post_compose(to_abs) + self.__complex_conjugation = K_rel.automorphisms()[1].pre_compose( + from_abs).post_compose(to_abs) self.__complex_conjugation = self.hom([self.__complex_conjugation(self.gen())], check=False) return self.__complex_conjugation else: @@ -3410,17 +3406,17 @@ def dirichlet_group(self): Dirichlet character modulo 11 of conductor 11 mapping 2 |--> zeta5^3 sage: X[4]^2 in X True - """ - #todo : turn this into an abelian group rather than a list. + # todo : turn this into an abelian group rather than a list. from sage.modular.dirichlet import DirichletGroup m = self.conductor() d = self.degree() - A = _splitting_classes_gens_(self,m,d) - # d could be improved to be the exponent of the Galois group rather than the degree, but I do not see how to go about that yet. + A = _splitting_classes_gens_(self, m, d) + # d could be improved to be the exponent of the Galois group + # rather than the degree, but I do not see how to go about that yet. G = DirichletGroup(m, CyclotomicField(d)) - H = [G(1)] + H = [G.one()] for chi in G: if len(H) == d: break @@ -3491,8 +3487,8 @@ def _latex_(self): \Bold{Q}[\theta_{25}]/(\theta_{25}^{25} + \theta_{25} + 1) """ latex_name = self.latex_variable_names()[0] - return "%s[%s]/(%s)"%(latex(QQ), latex_name, - self.polynomial()._latex_(latex_name)) + return "%s[%s]/(%s)" % (latex(QQ), latex_name, + self.polynomial()._latex_(latex_name)) def _ideal_class_(self, n=0): """ @@ -3549,7 +3545,6 @@ def ideal(self, *gens, **kwds): sage: K. = NumberField(x^6 - x^5 - 5*x^4 + 4*x^3 + 6*x^2 - 3*x - 1) sage: K.ideal(1,1) Fractional ideal (1) - """ try: return self.fractional_ideal(*gens, **kwds) @@ -3824,11 +3819,12 @@ def primes_above(self, x, degree=None): """ if degree is not None: degree = ZZ(degree) - facs = sorted([ (id.residue_class_degree(), id.absolute_norm(), id) for id in self.prime_factors(x) ]) + facs = sorted((id.residue_class_degree(), id.absolute_norm(), id) + for id in self.prime_factors(x)) if degree is None: - return [ id for d, n, id in facs ] + return [id for d, n, id in facs] else: - return [ id for d, n, id in facs if d == degree ] + return [id for d, n, id in facs if d == degree] def prime_above(self, x, degree=None): r""" @@ -3972,18 +3968,19 @@ def primes_of_bounded_norm(self, B): B = ZZ(B.ceil()) except (TypeError, AttributeError): raise TypeError("%s is not valid bound on prime ideals" % B) - if B<2: + if B < 2: return [] from sage.rings.fast_arith import prime_range if self is QQ: - #return arith.primes(B+1) - return prime_range(B+1, algorithm="pari_isprime") + # return arith.primes(B+1) + return prime_range(B + 1, algorithm="pari_isprime") else: - #P = [pp for p in arith.primes(B+1) for pp in self.primes_above(p)] - P = [pp for p in prime_range(B+1, algorithm="pari_isprime") for pp in self.primes_above(p)] + # P = [pp for p in arith.primes(B+1) for pp in self.primes_above(p)] + P = (pp for p in prime_range(B + 1, algorithm="pari_isprime") + for pp in self.primes_above(p)) P = [p for p in P if p.norm() <= B] - P.sort(key=lambda P: (P.norm(),P)) + P.sort(key=lambda P: (P.norm(), P)) return P def primes_of_bounded_norm_iter(self, B): @@ -4030,12 +4027,12 @@ def primes_of_bounded_norm_iter(self, B): from sage.rings.fast_arith import prime_range if self is QQ: - #for p in arith.primes(B+1): - for p in prime_range(B+1,algorithm="pari_isprime"): + # for p in arith.primes(B+1): + for p in prime_range(B + 1, algorithm="pari_isprime"): yield p else: - #for p in arith.primes(B+1): - for p in prime_range(B+1,algorithm="pari_isprime"): + # for p in arith.primes(B+1): + for p in prime_range(B + 1, algorithm="pari_isprime"): for pp in self.primes_above(p): if pp.norm() <= B: yield pp @@ -4099,7 +4096,6 @@ def primes_of_degree_one_list(self, n, num_integer_primes=10000, max_iterations= INPUT: - - ``num_integer_primes (default: 10000)`` - an integer. We try to find primes of absolute norm no greater than the num_integer_primes-th prime number. For example, if @@ -4108,8 +4104,7 @@ def primes_of_degree_one_list(self, n, num_integer_primes=10000, max_iterations= - ``max_iterations (default: 100)`` - an integer. We test max_iterations integers to find small primes before raising - StopIteration. - + ``StopIteration``. EXAMPLES:: @@ -4123,9 +4118,9 @@ def primes_of_degree_one_list(self, n, num_integer_primes=10000, max_iterations= [1, 1, 1] """ it = self.primes_of_degree_one_iter() - return [ next(it) for i in range(n) ] + return [next(it) for i in range(n)] - def completely_split_primes(self, B = 200): + def completely_split_primes(self, B=200): r""" Return a list of rational primes which split completely in the number field `K`. @@ -4150,7 +4145,7 @@ def completely_split_primes(self, B = 200): split_primes = [] for p in prime_range(B): Fp = GF(p) - FpT = PolynomialRing(Fp,'T') + FpT = PolynomialRing(Fp, 'T') g = FpT(self.defining_polynomial()) if len(factor(g)) == self.degree(): split_primes.append(p) @@ -4530,16 +4525,15 @@ def _gap_init_(self): [ tau ] sage: gap(tau)^3 !2 - """ if not self.is_absolute(): raise NotImplementedError("Currently, only simple algebraic extensions are implemented in gap") G = sage.interfaces.gap.gap q = self.polynomial() - if q.variable_name()!='E': - return 'CallFuncList(function() local %s,E; %s:=Indeterminate(%s,"%s"); E:=AlgebraicExtension(%s,%s,"%s"); return E; end,[])'%(q.variable_name(),q.variable_name(),G(self.base_ring()).name(),q.variable_name(),G(self.base_ring()).name(),repr(self.polynomial()),str(self.gen())) - else: - return 'CallFuncList(function() local %s,F; %s:=Indeterminate(%s,"%s"); F:=AlgebraicExtension(%s,%s,"%s"); return F; end,[])'%(q.variable_name(),q.variable_name(),G(self.base_ring()).name(),q.variable_name(),G(self.base_ring()).name(),repr(self.polynomial()),str(self.gen())) + if q.variable_name() != 'E': + return 'CallFuncList(function() local %s,E; %s:=Indeterminate(%s,"%s"); E:=AlgebraicExtension(%s,%s,"%s"); return E; end,[])' % (q.variable_name(), q.variable_name(), G(self.base_ring()).name(), q.variable_name(), G(self.base_ring()).name(), repr(self.polynomial()), str(self.gen())) + + return 'CallFuncList(function() local %s,F; %s:=Indeterminate(%s,"%s"); F:=AlgebraicExtension(%s,%s,"%s"); return F; end,[])' % (q.variable_name(), q.variable_name(), G(self.base_ring()).name(), q.variable_name(), G(self.base_ring()).name(), repr(self.polynomial()), str(self.gen())) def characteristic(self): """ @@ -4624,10 +4618,10 @@ def class_group(self, proof=None, names='c'): except AttributeError: self.__class_group = {} k = self.pari_bnf(proof) - cycle_structure = tuple( ZZ(c) for c in k.bnf_get_cyc() ) + cycle_structure = tuple(ZZ(c) for c in k.bnf_get_cyc()) # Gens is a list of ideals (the generators) - gens = tuple( self.ideal(hnf) for hnf in k.bnf_get_gen() ) + gens = tuple(self.ideal(hnf) for hnf in k.bnf_get_gen()) G = ClassGroup(cycle_structure, names, self, gens, proof=proof) self.__class_group[proof, names] = G @@ -5030,7 +5024,7 @@ def selmer_generators(self, S, m, proof=True, orders=False): ords.append(m) else: m1 = order.gcd(m) - if m1!= 1: + if m1 != 1: gens.append(unit) ords.append(m1) card_S = len(S) @@ -5584,8 +5578,8 @@ def composite_fields(self, other, names=None, both_maps=False, preserve_embeddin i -= 1 else: self_to_F = self.hom([a_in_F]) - other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(F), other_abs_to_F*to_other_abs) - rets.append( (F, self_to_F, other_to_F, k) ) + other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(F), other_abs_to_F * to_other_abs) + rets.append((F, self_to_F, other_to_F, k)) else: rets.append(F) return rets @@ -5818,7 +5812,7 @@ def extension(self, poly, name=None, names=None, latex_name=None, latex_names=No try: poly = poly.polynomial(self) except (AttributeError, TypeError): - raise TypeError("polynomial (=%s) must be a polynomial."%repr(poly)) + raise TypeError("polynomial (=%s) must be a polynomial" % repr(poly)) if poly.base_ring() is not self: poly = poly.change_ring(self) if names is not None: @@ -6139,7 +6133,6 @@ def is_abelian(self): sage: NumberField(x^6 + x^5 - 5*x^4 - 4*x^3 + 6*x^2 + 3*x - 1, 'a').is_abelian() True """ - if not self.is_galois(): return False @@ -6152,7 +6145,7 @@ def is_abelian(self): return self.galois_group().is_abelian() pari_pol = pari(self.polynomial()) - return pari_pol.galoisinit().galoisisabelian(1)==1 + return pari_pol.galoisinit().galoisisabelian(1) == 1 @cached_method def galois_group(self, type=None, algorithm='pari', names=None, gc_numbering=None): @@ -6324,7 +6317,7 @@ def power_basis(self): [1, zeta15, zeta15^2, zeta15^3, zeta15^4, zeta15^5, zeta15^6, zeta15^7] """ g = self.gen() - return [ g**i for i in range(self.relative_degree()) ] + return [g**i for i in range(self.relative_degree())] def integral_basis(self, v=None): """ @@ -6333,11 +6326,9 @@ def integral_basis(self, v=None): INPUT: - - ``v`` - None, a prime, or a list of primes. See the documentation for self.maximal_order. - EXAMPLES:: sage: K. = NumberField(x^5 + 10*x + 1) @@ -6421,7 +6412,7 @@ def _pari_integral_basis(self, v=None, important=True): trialdivlimit2 = pari(10**12) trialdivlimit3 = pari(10**18) if all(p < trialdivlimit2 or (e == 1 and p < trialdivlimit3) or p.isprime() for p, e in zip(m[0], m[1])): - B = f.nfbasis(fa = m) + B = f.nfbasis(fa=m) else: raise RuntimeError("Unable to factor discriminant with trial division") else: @@ -6578,25 +6569,25 @@ def reduced_gram_matrix(self, prec=None): if self.is_totally_real(): B = self.reduced_basis() self.__reduced_gram_matrix = matrix(ZZ, d, d, - [[(x*y).trace() for x in B] + [[(x * y).trace() for x in B] for y in B]) else: M = self.minkowski_embedding(prec=prec) - T = matrix(d, flatten([ a.vector().list() - for a in self.reduced_basis(prec=prec) ])) - A = M*(T.transpose()) - self.__reduced_gram_matrix = A.transpose()*A + T = matrix(d, flatten([a.vector().list() + for a in self.reduced_basis(prec=prec)])) + A = M * T.transpose() + self.__reduced_gram_matrix = A.transpose() * A if prec is None: - ## this is the default choice for minkowski_embedding + # this is the default choice for minkowski_embedding self.__reduced_gram_matrix_prec = 53 else: self.__reduced_gram_matrix_prec = prec return self.__reduced_gram_matrix - #****************************************************** - # Supplementary algorithm to enumerate lattice points - #****************************************************** + # ****************************************************** + # Supplementary algorithm to enumerate lattice points + # ****************************************************** def _positive_integral_elements_with_trace(self, C): r""" @@ -6632,14 +6623,14 @@ def _positive_integral_elements_with_trace(self, C): B = self.reduced_basis() T = self.reduced_gram_matrix() - P = pari(T).qfminim((C[1]**2)*(1./2), 10**6)[2] + P = pari(T).qfminim((C[1]**2) * 0.5, 10**6)[2] S = [] for p in P: - theta = sum([ p.list()[i]*B[i] for i in range(self.degree())]) + theta = sum([p.list()[i] * B[i] for i in range(self.degree())]) if theta.trace() < 0: theta *= -1 - if theta.trace() >= C[0] and theta.trace() <= C[1]: + if C[0] <= theta.trace() <= C[1]: if self(theta).is_totally_positive(): S.append(self(theta)) return S @@ -6886,7 +6877,7 @@ def residue_field(self, prime, names=None, check=True): """ from sage.rings.number_field.number_field_ideal import is_NumberFieldIdeal if is_NumberFieldIdeal(prime) and prime.number_field() is not self: - raise ValueError("%s is not an ideal of %s"%(prime,self)) + raise ValueError("%s is not an ideal of %s" % (prime, self)) # This allows principal ideals to be specified using a generator: try: prime = self.ideal(prime) @@ -6894,9 +6885,9 @@ def residue_field(self, prime, names=None, check=True): pass if not is_NumberFieldIdeal(prime) or prime.number_field() is not self: - raise ValueError("%s is not an ideal of %s"%(prime,self)) + raise ValueError("%s is not an ideal of %s" % (prime, self)) if check and not prime.is_prime(): - raise ValueError("%s is not a prime ideal"%prime) + raise ValueError("%s is not a prime ideal" % prime) from sage.rings.finite_rings.residue_field import ResidueField return ResidueField(prime, names=names, check=False) @@ -7277,14 +7268,14 @@ def S_unit_group(self, proof=None, S=None): try: S = tuple(self.ideal(S).prime_factors()) except (NameError, TypeError, ValueError): - raise ValueError("Cannot make a set of primes from %s"%(S,)) + raise ValueError(f"Cannot make a set of primes from {S}") else: try: S = tuple(self.ideal(P) for P in S) except (NameError, TypeError, ValueError): - raise ValueError("Cannot make a set of primes from %s"%(S,)) + raise ValueError(f"Cannot make a set of primes from {S}") if not all(P.is_prime() for P in S): - raise ValueError("Not all elements of %s are prime ideals"%(S,)) + raise ValueError(f"Not all elements of {S} are prime ideals") try: return self._S_unit_group_cache[S] @@ -7592,7 +7583,7 @@ def roots_of_unity(self): """ z = self.primitive_root_of_unity() n = self.zeta_order() - return [ z**k for k in range(1, n+1) ] + return [z**k for k in range(1, n + 1)] def zeta_coefficients(self, n): """ @@ -7648,11 +7639,11 @@ def solve_CRT(self, reslist, Ilist, check=True): reslist = [self(x) for x in reslist] except ValueError: raise ValueError("solve_CRT requires a list of arguments in the field") - if n==0: + if n == 0: return self.zero() - if n==1: + if n == 1: return reslist[0] - if n==2: + if n == 2: try: r = Ilist[0].element_1_mod(Ilist[1]) except TypeError: @@ -7980,7 +7971,7 @@ def __init__(self, polynomial, name, latex_name=None, check=True, embedding=None assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes, structure=structure) self._element_class = number_field_element.NumberFieldElement_absolute self._zero_element = self._element_class(self, 0) - self._one_element = self._element_class(self, 1) + self._one_element = self._element_class(self, 1) self._init_embedding_approx() @@ -8153,22 +8144,22 @@ def _coerce_from_other_number_field(self, x): F = LF Kgen = F(Kgen) else: - raise TypeError("No compatible natural embeddings found for %s and %s"%(KF,LF)) + raise TypeError("No compatible natural embeddings found for %s and %s" % (KF, LF)) # List of candidates for K(x) f = x.minpoly() ys = f.roots(ring=K, multiplicities=False) if not ys: - raise ValueError("Cannot convert %s to %s (regardless of embeddings)"%(x,K)) + raise ValueError("Cannot convert %s to %s (regardless of embeddings)" % (x, K)) # Define a function are_roots_equal to determine whether two # roots of f are equal. A simple a == b does not suffice for # inexact fields because of floating-point errors. if F.is_exact(): - are_roots_equal = lambda a,b: a == b + are_roots_equal = lambda a, b: a == b else: - ### Compute a lower bound on the distance between the roots of f. - ### This essentially gives the precision to work with. + # Compute a lower bound on the distance between the roots of f. + # This essentially gives the precision to work with. # A function # log2abs: F --> RR @@ -8180,7 +8171,7 @@ def _coerce_from_other_number_field(self, x): # Compute half Fujiwara's bound on the roots of f n = f.degree() log_half_root_bound = log2abs(f[0]/2)/n - for i in range(1,n): + for i in range(1, n): bd = log2abs(f[i])/(n-i) if bd > log_half_root_bound: log_half_root_bound = bd @@ -8216,7 +8207,7 @@ def _coerce_from_other_number_field(self, x): emb_y = y.polynomial()(Kgen) if are_roots_equal(emb_x, emb_y): return y - raise ValueError("Cannot convert %s to %s (using the specified embeddings)"%(x,K)) + raise ValueError("Cannot convert %s to %s (using the specified embeddings)" % (x, K)) def _coerce_map_from_(self, R): """ @@ -8315,7 +8306,7 @@ def _coerce_map_from_(self, R): if self.coerce_embedding() is not None: try: return number_field_morphisms.EmbeddedNumberFieldMorphism(R, self) - except ValueError: # no common embedding found + except ValueError: # no common embedding found return None else: # R is embedded, self isn't. So, we could only have @@ -8744,12 +8735,12 @@ def _subfields_helper(self, degree=0, name=None, both_maps=True, optimize=False) embedding = self.coerce_embedding()(a) # trac 7695 add a _ to prevent zeta70 etc. if name[-1].isdigit(): - new_name= name+ '_' + str(i) + new_name = name + '_' + str(i) else: new_name = name + str(i) K = NumberField(f, names=new_name, embedding=embedding) - from_K = K.hom([a]) # check=False here ?? would be safe unless there are bugs. + from_K = K.hom([a]) # check=False here ?? would be safe unless there are bugs. if both_maps and K.degree() == self.degree(): g = K['x'](self.polynomial()) @@ -9007,7 +8998,7 @@ def _galois_closure_and_embedding(self, names=None): L = self.__galois_closure.change_names(names) L_to_orig, orig_to_L = L.structure() # "flatten" the composition by hand - self_into_L = self.hom([ (orig_to_L * self.__galois_closure_embedding)(self.gen()) ]) + self_into_L = self.hom([(orig_to_L * self.__galois_closure_embedding)(self.gen())]) return (L, self_into_L) except AttributeError: pass @@ -9081,17 +9072,14 @@ def galois_closure(self, names=None, map=False): Defn: a |--> 1/240*cc^5 - 41/120*cc """ L, self_into_L = self._galois_closure_and_embedding(names) - if map: - return (L, self_into_L) - else: - return L + return (L, self_into_L) if map else L def automorphisms(self): r""" Compute all Galois automorphisms of self. - This uses PARI's :pari:`nfgaloisconj` and is much faster than root finding - for many fields. + This uses PARI's :pari:`nfgaloisconj` and is much faster than + root finding for many fields. EXAMPLES:: @@ -9144,12 +9132,12 @@ def embeddings(self, K): not even be a number field, e.g., it could be the complex numbers). This will return an identical result when given K as input again. - If possible, the most natural embedding of this field into K is put first - in the list. + If possible, the most natural embedding of this field into K + is put first in the list. INPUT: - - ``K`` - a field + - ``K`` -- a field EXAMPLES:: @@ -9225,7 +9213,7 @@ def embeddings(self, K): # If there is an embedding that preserves variable names # then it is most natural, so we put it first. put_natural_embedding_first(v) - return Sequence(v, cr=v!=[], immutable=True, + return Sequence(v, cr=bool(v), immutable=True, check=False, universe=self.Hom(K)) def minkowski_embedding(self, B=None, prec=None): @@ -9287,14 +9275,14 @@ def minkowski_embedding(self, B=None, prec=None): R = sage.rings.real_double.RDF else: R = sage.rings.real_mpfr.RealField(prec) - r,s = self.signature() + r, s = self.signature() places = self.places(prec=prec) if B is None: - B = [ (self.gen(0))**i for i in range(n) ] + B = [(self.gen(0))**i for i in range(n)] A = ZZ['x'] - f = A.gen(0)**2-2 + f = A.gen(0)**2 - 2 sqrt2 = f.roots(R)[1][0] d = {} @@ -9302,7 +9290,7 @@ def minkowski_embedding(self, B=None, prec=None): for col in range(n): for row in range(r): - d[(row,col)] = places[row](B[col]) + d[(row, col)] = places[row](B[col]) for i in range(s): z = places[r+i](B[col]) @@ -9468,27 +9456,28 @@ def places(self, all_complex=False, prec=None): R = sage.rings.real_mpfr.RealField(prec) C = sage.rings.complex_mpfr.ComplexField(prec) - ## first, find the intervals with roots, and see how much - ## precision we need to approximate the roots - ## - all_intervals = [ x[0] for x in self.defining_polynomial().roots(C) ] + # first, find the intervals with roots, and see how much + # precision we need to approximate the roots + # + all_intervals = [x[0] for x in self.defining_polynomial().roots(C)] - ## first, set up the real places + # first, set up the real places if all_complex: - real_intervals = [ x for x in all_intervals if x.imag().is_zero() ] + real_intervals = [x for x in all_intervals if x.imag().is_zero()] else: - real_intervals = [ x[0] for x in self.defining_polynomial().roots(R) ] + real_intervals = [x[0] for x in self.defining_polynomial().roots(R)] if prec is None: - real_places = [ self.hom([i.center()], check=False) for i in real_intervals ] + real_places = [self.hom([i.center()], check=False) + for i in real_intervals] - complex_places = [ self.hom([i.center()], check=False) for i in - all_intervals if i.imag() > 0 ] + complex_places = [self.hom([i.center()], check=False) + for i in all_intervals if i.imag() > 0] else: - real_places = [ self.hom([i], check=False) for i in real_intervals ] + real_places = [self.hom([i], check=False) for i in real_intervals] - complex_places = [ self.hom([i], check=False) for i in - all_intervals if i.imag() > 0 ] + complex_places = [self.hom([i], check=False) + for i in all_intervals if i.imag() > 0] return real_places + complex_places @@ -9768,12 +9757,12 @@ def relativize(self, alpha, names, structure=None): if alpha.codomain() != self: raise ValueError("Co-domain of morphism must be self") L = alpha.domain() - alpha = alpha(L.gen()) # relativize over phi's domain + alpha = alpha(L.gen()) # relativize over phi's domain if L is QQ: from sage.rings.polynomial.polynomial_ring import polygen f = polygen(QQ) else: - f = L.defining_polynomial() # = alpha.minpoly() + f = L.defining_polynomial() # = alpha.minpoly() names = normalize_names(len(names), names) else: # alpha must be an element coercible to self @@ -9785,13 +9774,14 @@ def relativize(self, alpha, names, structure=None): # now we do some linear algebra to find the minpoly of self.gen() over L L_into_self = L.hom([alpha]) - extdeg = self.absolute_degree() // L.absolute_degree() # [ L : self ] + extdeg = self.absolute_degree() // L.absolute_degree() # [ L : self ] a = self.gen() # we will find a linear relation between small powers of a over L - basis = [ a**i * b for i in range(extdeg) for b in map(L_into_self, L.power_basis()) ] - basis.append(a**extdeg) # this one makes the basis no longer a basis - mat = matrix([ b.vector() for b in basis ]) + basis = [a**i * b for i in range(extdeg) + for b in map(L_into_self, L.power_basis())] + basis.append(a**extdeg) # this one makes the basis no longer a basis + mat = matrix([b.vector() for b in basis]) soln_space = mat.left_kernel(mat.row_space()(0)) # the solution space is one dimensional and the last entry is non-zero # because a satisfies no smaller linear relation @@ -9801,8 +9791,8 @@ def relativize(self, alpha, names, structure=None): reln = reln * ~reln[-1] # now we need to get those coeffs in L - coeff_mat = matrix(extdeg, f.degree(), list(reln)[:-1]) # easy way to divide into the correct lengths - coeffs_in_L = [ r*vector(L.power_basis()) for r in coeff_mat.rows() ] + coeff_mat = matrix(extdeg, f.degree(), list(reln)[:-1]) # easy way to divide into the correct lengths + coeffs_in_L = [r * vector(L.power_basis()) for r in coeff_mat.rows()] # f is the minimal polynomial of a over L f = L['x'](coeffs_in_L + [1]) # sanity check... @@ -9923,7 +9913,7 @@ def relative_different(self): """ return self.different() - def hilbert_symbol(self, a, b, P = None): + def hilbert_symbol(self, a, b, P=None): r""" Return the Hilbert symbol `(a,b)_P` for a prime P of self and non-zero elements a and b of self. @@ -10134,7 +10124,7 @@ def hilbert_symbol(self, a, b, P = None): codom = P.codomain() from sage.rings.qqbar import AA, QQbar if isinstance(codom, (sage.rings.abc.ComplexField, sage.rings.abc.ComplexDoubleField, sage.rings.abc.ComplexIntervalField)) or \ - codom is QQbar: + codom is QQbar: if P(self.gen()).imag() == 0: raise ValueError("Possibly real place (=%s) given as complex embedding in hilbert_symbol. Is it real or complex?" % P) return 1 @@ -10219,7 +10209,7 @@ def hilbert_symbol_negative_at_S(self, S, b, check=True): # input checks if not type(S) is list: - raise TypeError( "first argument must be a list") + raise TypeError("first argument must be a list") if b not in self: raise TypeError("second argument must be an element of this field") b = self(b) @@ -10233,15 +10223,15 @@ def hilbert_symbol_negative_at_S(self, S, b, check=True): if not p.is_prime(): raise ValueError("not a prime ideal") if self.quadratic_defect(b, p) == infinity.Infinity: - raise ValueError("%s is a square in the completion "%b + - "with respect to %s"%p) + raise ValueError(f"{b} is a square in the completion " + f"with respect to {p}") else: if p not in self.real_places(): raise ValueError("entries of the list must be " "prime ideals or real places") if p(b) > 0: - raise ValueError("%s is a square in the completion " - "with respect to %s" % (b, p)) + raise ValueError(f"{b} is a square in the completion " + f"with respect to {p}") # L is the list of primes that we need to consider, b must have # nonzero valuation for each prime in L, this is the set S' @@ -10353,7 +10343,6 @@ def hilbert_conductor(self,a,b): AUTHOR: - Aly Deines - """ a, b = self(a), self(b) d = self.ideal(1) @@ -10625,22 +10614,22 @@ def __init__(self, n, names, embedding=None, assume_disc_small=False, maximize_a """ f = QQ['x'].cyclotomic_polynomial(n) if names[0].startswith('zeta'): - latex_name = "\\zeta_{%s}"%n + latex_name = "\\zeta_{%s}" % n else: latex_name = latex_variable_name(names[0]) self.__n = n = Integer(n) NumberField_absolute.__init__(self, f, - name= names, + name=names, latex_name=latex_name, check=False, - embedding = embedding, + embedding=embedding, assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes) if n % 2: self.__zeta_order = 2 * n else: self.__zeta_order = n - ## quadratic number fields require this: + # quadratic number fields require this: if f.degree() == 2: # define a boolean flag as for NumberField_quadratic to know, which # square root we choose (True means no embedding or positive @@ -10656,12 +10645,12 @@ def __init__(self, n, names, embedding=None, assume_disc_small=False, maximize_a if n == 4: self._element_class = number_field_element_quadratic.NumberFieldElement_gaussian self._D = ZZ(-1) - self._NumberField_generic__gen = self._element_class(self, (QQ(0), QQ(1))) + self._NumberField_generic__gen = self._element_class(self, (QQ(0), QQ.one())) else: - ## n is 3 or 6 + # n is 3 or 6 self._element_class = number_field_element_quadratic.NumberFieldElement_quadratic self._D = ZZ(-3) - one_half = ZZ(1)/ZZ(2) + one_half = QQ((1, 2)) if n == 3: self._NumberField_generic__gen = self._element_class(self, (one_half-1, one_half)) else: @@ -10671,8 +10660,8 @@ def __init__(self, n, names, embedding=None, assume_disc_small=False, maximize_a # _one_element to NumberFieldElement_absolute values, which is # wrong (and dangerous; such elements can actually be used to # crash Sage: see #5316). Overwrite them with correct values. - self._zero_element = self._element_class(self, (QQ(0),QQ(0))) - self._one_element = self._element_class(self, (QQ(1),QQ(0))) + self._zero_element = self._element_class(self, (QQ(0), QQ(0))) + self._one_element = self._element_class(self, (QQ.one(), QQ(0))) zeta = self.gen() zeta._set_multiplicative_order(n) @@ -10753,7 +10742,7 @@ def _magma_init_(self, magma): sage: K._magma_init_(magma) # optional - magma 'SageCreateWithNames(CyclotomicField(7),["zeta"])' """ - s = 'CyclotomicField(%s)'%self.__n + s = 'CyclotomicField(%s)' % self.__n return magma._with_names(s, self.variable_names()) def _gap_init_(self): @@ -10786,7 +10775,7 @@ def _gap_init_(self): zeta3 -zeta3 - 1 """ - return 'CyclotomicField(%s)'%self.__n + return 'CyclotomicField(%s)' % self.__n def _libgap_(self): """ @@ -10819,8 +10808,8 @@ def _repr_(self): sage: CyclotomicField(400)._repr_() 'Cyclotomic Field of order 400 and degree 160' """ - return "Cyclotomic Field of order %s and degree %s"%( - self.__n, self.degree()) + n = self._n + return f"Cyclotomic Field of order {n} and degree {self.degree()}" def _n(self): """ @@ -10871,9 +10860,8 @@ def _latex_(self): """ v = self.latex_variable_names()[0] if v.startswith('\\zeta_'): - return "%s(%s)"%(latex(QQ), v) - else: - return NumberField_generic._latex_(self) + return "%s(%s)" % (latex(QQ), v) + return NumberField_generic._latex_(self) def _coerce_map_from_(self, K): r""" @@ -11171,86 +11159,86 @@ def _element_constructor_(self, x, check=True): # breaks the doctests in eisenstein_submodule.py. # FIX THIS. -## def _will_be_better_coerce_from_other_cyclotomic_field(self, x, only_canonical=False): -## """ -## Coerce an element x of a cyclotomic field into self, if at all possible. +# def _will_be_better_coerce_from_other_cyclotomic_field(self, x, only_canonical=False): +# """ +# Coerce an element x of a cyclotomic field into self, if at all possible. -## INPUT: -## x -- number field element +# INPUT: +# x -- number field element -## only_canonical -- bool (default: False); Attempt to work, -## even in some cases when x is not in a subfield of -## the cyclotomics (as long as x is a root of unity). +# only_canonical -- bool (default: False); Attempt to work, +# even in some cases when x is not in a subfield of +# the cyclotomics (as long as x is a root of unity). -## EXAMPLES:: +# EXAMPLES:: -## sage: k5 = CyclotomicField(5) -## sage: k3 = CyclotomicField(3) -## sage: k15 = CyclotomicField(15) -## sage: k15._coerce_from_other_cyclotomic_field(k3.gen()) -## zeta15^5 -## sage: k15._coerce_from_other_cyclotomic_field(k3.gen()^2 + 17/3) -## -zeta15^5 + 14/3 -## sage: k3._coerce_from_other_cyclotomic_field(k15.gen()^5) -## zeta3 -## sage: k3._coerce_from_other_cyclotomic_field(-2/3 * k15.gen()^5 + 2/3) -## -2/3*zeta3 + 2/3 -## """ +# sage: k5 = CyclotomicField(5) +# sage: k3 = CyclotomicField(3) +# sage: k15 = CyclotomicField(15) +# sage: k15._coerce_from_other_cyclotomic_field(k3.gen()) +# zeta15^5 +# sage: k15._coerce_from_other_cyclotomic_field(k3.gen()^2 + 17/3) +# -zeta15^5 + 14/3 +# sage: k3._coerce_from_other_cyclotomic_field(k15.gen()^5) +# zeta3 +# sage: k3._coerce_from_other_cyclotomic_field(-2/3 * k15.gen()^5 + 2/3) +# -2/3*zeta3 + 2/3 +# """ -## K = x.parent() +# K = x.parent() -## if K is self: -## return x -## n = K.zeta_order() -## m = self.zeta_order() +# if K is self: +# return x +# n = K.zeta_order() +# m = self.zeta_order() -## self_gen = self.gen() +# self_gen = self.gen() -## if m % n == 0: # easy case -## # pass this off to a method in the element class -## # it can be done very quickly and easily by the cython<->NTL -## # interface there -## return x._lift_cyclotomic_element(self) +# if m % n == 0: # easy case +# # pass this off to a method in the element class +# # it can be done very quickly and easily by the cython<->NTL +# # interface there +# return x._lift_cyclotomic_element(self) -## # Whatever happens below, it has to be consistent with -## # zeta_r |---> (zeta_s)^m +# # Whatever happens below, it has to be consistent with +# # zeta_r |---> (zeta_s)^m -## if m % 2 and not n%2: -## m *= 2 -## self_gen = -self_gen +# if m % 2 and not n%2: +# m *= 2 +# self_gen = -self_gen -## if only_canonical and m % n: -## raise TypeError, "no canonical coercion" +# if only_canonical and m % n: +# raise TypeError, "no canonical coercion" -## if not is_CyclotomicField(K): -## raise TypeError, "x must be in a cyclotomic field" +# if not is_CyclotomicField(K): +# raise TypeError, "x must be in a cyclotomic field" -## v = x.list() +# v = x.list() -## # Find the smallest power r >= 1 of the generator g of K that is in self, -## # i.e., find the smallest r such that g^r has order dividing m. +# # Find the smallest power r >= 1 of the generator g of K that is in self, +# # i.e., find the smallest r such that g^r has order dividing m. -## d = sage.arith.all.gcd(m,n) -## r = n // d +# d = sage.arith.all.gcd(m,n) +# r = n // d -## # Since we use the power basis for cyclotomic fields, if every -## # v[i] with i not divisible by r is 0, then we're good. +# # Since we use the power basis for cyclotomic fields, if every +# # v[i] with i not divisible by r is 0, then we're good. -## # If h generates self and has order m, then the element g^r -## # maps to the power of self of order gcd(m,n)., i.e., h^(m/gcd(m,n)) -## # -## z = self_gen**(m // d) -## w = self(1) +# # If h generates self and has order m, then the element g^r +# # maps to the power of self of order gcd(m,n)., i.e., h^(m/gcd(m,n)) +# # +# z = self_gen**(m // d) +# w = self(1) -## a = self(0) -## for i in range(len(v)): -## if i%r: -## if v[i]: -## raise TypeError, "element does not belong to cyclotomic field" -## else: -## a += w*v[i] -## w *= z -## return a +# a = self(0) +# for i in range(len(v)): +# if i%r: +# if v[i]: +# raise TypeError, "element does not belong to cyclotomic field" +# else: +# a += w*v[i] +# w *= z +# return a def _coerce_from_other_cyclotomic_field(self, x, only_canonical=False): """ @@ -11588,9 +11576,9 @@ def signature(self): """ m = ZZ(self.degree()) if m == 1: - return (ZZ(1), ZZ(0)) + return (ZZ.one(), ZZ(0)) else: - return (ZZ(0), ZZ(m/2)) + return (ZZ(0), m // 2) def different(self): """ @@ -11654,17 +11642,17 @@ def discriminant(self, v=None): except AttributeError: n = self._n() deg = self.degree() - d = ZZ(1) # so that CyclotomicField(1).disc() has the right type + d = ZZ.one() # so that CyclotomicField(1).disc() has the right type factors = n.factor() for (p, r) in factors: - e = (r*p - r - 1) * deg // (p-1) + e = (r * p - r - 1) * deg // (p - 1) d *= p**e sign = 1 if len(factors) == 1 and (n == 4 or factors[0][0].mod(4) == 3): sign = -1 elif len(factors) == 2 and factors[0] == (2, 1) and factors[1][0].mod(4) == 3: sign = -1 - self.__disc = sign*d + self.__disc = sign * d return self.__disc else: return NumberField_generic.discriminant(self, v) @@ -11842,18 +11830,18 @@ def zeta(self, n=None, all=False): if n % 2 == 0 and m % 2 == 1: # In the n-th cyclotomic field, n odd, there are # actually 2*n-th roots of unity, so we include them. - z = -z**((m+1)//2) # -z - m = 2*m + z = -z**((m+1)//2) # -z + m = 2 * m if m % n != 0: raise ValueError("%s does not divide order of generator (%s)" % - (n, self.zeta_order())) - a = z**(m//n) + (n, self.zeta_order())) + a = z**(m // n) if not all: return a v = [a] - b = a*a - for i in range(2,n): + b = a * a + for i in range(2, n): if n.gcd(i).is_one(): v.append(b) b = b * a @@ -11870,7 +11858,7 @@ def number_of_roots_of_unity(self): 42 """ n = self._n() - if n%2: + if n % 2: n *= 2 return n @@ -11884,13 +11872,12 @@ def roots_of_unity(self): sage: K. = CyclotomicField(3) sage: zs = K.roots_of_unity(); zs [1, a, -a - 1, -1, -a, a + 1] - sage: [ z**K.number_of_roots_of_unity() for z in zs ] + sage: [z**K.number_of_roots_of_unity() for z in zs] [1, 1, 1, 1, 1, 1] """ - z = self.gen() n = self._n() - v = [z**k for k in range(n)] - if n%2: + v = self.gen().powers(n) + if n % 2: v += [-x for x in v] return v @@ -12008,7 +11995,7 @@ def _coerce_map_from_(self, K): if K is ZZ: return number_field_element_quadratic.Z_to_quadratic_field_element(self) if K is int: - return self._coerce_map_via([ZZ], int) # faster than direct + return self._coerce_map_via([ZZ], int) # faster than direct if K is QQ: return number_field_element_quadratic.Q_to_quadratic_field_element(self) return NumberField_absolute._coerce_map_from_(self, K) @@ -12029,7 +12016,7 @@ def _latex_(self): """ v = self.latex_variable_names()[0] if v.startswith('\\sqrt'): - return "%s(%s)"%(latex(QQ), v) + return "%s(%s)" % (latex(QQ), v) else: return NumberField_generic._latex_(self) @@ -12281,8 +12268,8 @@ def is_fundamental_discriminant(D): if d not in [0, 1]: return False return D != 1 and D != 0 and \ - (arith.is_squarefree(D) or \ - (d == 0 and (D//4)%4 in [2,3] and arith.is_squarefree(D//4))) + (arith.is_squarefree(D) or + (d == 0 and (D // 4) % 4 in [2, 3] and arith.is_squarefree(D // 4))) ################### From fd5f450cdf260f8d107357a0193ba5d9f0eb55e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 31 Oct 2022 20:45:41 +0100 Subject: [PATCH 331/414] fix the doc --- src/sage/categories/number_fields.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sage/categories/number_fields.py b/src/sage/categories/number_fields.py index b54a38dbaa2..c29e4c57255 100644 --- a/src/sage/categories/number_fields.py +++ b/src/sage/categories/number_fields.py @@ -220,10 +220,7 @@ def _test_absolute_disc(self, **options): - ``options`` -- any keyword arguments accepted by :meth:`_tester` - EXAMPLES: - - By default, this method tests only the elements returned by - ``self.some_elements()``:: + EXAMPLES:: sage: S = NumberField(x**3-x-1, 'a') sage: S._test_absolute_disc() From a1b70f3da76e4e2324b6b1c7cf3765bbc4f7d737 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Mon, 31 Oct 2022 23:09:18 +0900 Subject: [PATCH 332/414] Keep objects entries in toc --- .../algebras/cubic_hecke_algebra.rst | 2 +- src/doc/en/reference/algebras/index.rst | 16 +++++----- .../en/reference/algebras/lie_algebras.rst | 2 +- .../en/reference/algebras/quantum_groups.rst | 2 +- src/doc/en/reference/arithgroup/index.rst | 2 +- .../en/reference/arithmetic_curves/index.rst | 16 +++++----- src/doc/en/reference/categories/index.rst | 16 +++++----- src/doc/en/reference/coding/index.rst | 8 ++--- src/doc/en/reference/coercion/index.rst | 2 +- src/doc/en/reference/constants/index.rst | 2 +- src/doc/en/reference/cpython/index.rst | 2 +- src/doc/en/reference/cryptography/index.rst | 2 +- src/doc/en/reference/curves/index.rst | 6 ++-- .../en/reference/data_structures/index.rst | 2 +- .../diophantine_approximation/index.rst | 2 +- src/doc/en/reference/doctest/index.rst | 2 +- .../reference/dynamics/cellular_automata.rst | 2 +- .../reference/dynamics/complex_dynamics.rst | 2 +- src/doc/en/reference/dynamics/index.rst | 4 +-- .../en/reference/euclidean_spaces/index.rst | 3 +- src/doc/en/reference/finance/index.rst | 2 +- src/doc/en/reference/finite_rings/index.rst | 14 ++++----- src/doc/en/reference/functions/index.rst | 2 +- src/doc/en/reference/game_theory/index.rst | 2 +- src/doc/en/reference/games/index.rst | 2 +- src/doc/en/reference/groups/index.rst | 14 ++++----- src/doc/en/reference/hecke/index.rst | 2 +- src/doc/en/reference/homology/index.rst | 2 +- src/doc/en/reference/interfaces/index.rst | 2 +- src/doc/en/reference/knots/index.rst | 2 +- src/doc/en/reference/lfunctions/index.rst | 2 +- src/doc/en/reference/libs/index.rst | 30 +++++++++---------- src/doc/en/reference/logic/index.rst | 2 +- src/doc/en/reference/manifolds/chart.rst | 2 +- .../en/reference/manifolds/continuous_map.rst | 2 +- .../reference/manifolds/degenerate_metric.rst | 2 +- src/doc/en/reference/manifolds/diff_form.rst | 2 +- .../en/reference/manifolds/diff_manifold.rst | 2 +- src/doc/en/reference/manifolds/diff_map.rst | 4 +-- .../reference/manifolds/diff_scalarfield.rst | 2 +- .../manifolds/diff_vector_bundle.rst | 2 +- .../reference/manifolds/euclidean_space.rst | 2 +- src/doc/en/reference/manifolds/manifold.rst | 2 +- src/doc/en/reference/manifolds/mixed_form.rst | 2 +- .../en/reference/manifolds/multivector.rst | 2 +- .../en/reference/manifolds/scalarfield.rst | 2 +- .../en/reference/manifolds/tangent_space.rst | 2 +- .../en/reference/manifolds/tensorfield.rst | 2 +- .../en/reference/manifolds/vector_bundle.rst | 2 +- .../en/reference/manifolds/vectorfield.rst | 2 +- src/doc/en/reference/matrices/index.rst | 2 +- src/doc/en/reference/modabvar/index.rst | 2 +- src/doc/en/reference/modfrm/index.rst | 4 +-- .../reference/modfrm_hecketriangle/index.rst | 2 +- src/doc/en/reference/modmisc/index.rst | 2 +- src/doc/en/reference/modsym/index.rst | 4 +-- src/doc/en/reference/modules/index.rst | 18 +++++------ src/doc/en/reference/monoids/index.rst | 2 +- .../noncommutative_polynomial_rings/index.rst | 4 +-- src/doc/en/reference/padics/index.rst | 2 +- src/doc/en/reference/parallel/index.rst | 2 +- src/doc/en/reference/plot3d/index.rst | 10 +++---- src/doc/en/reference/plotting/index.rst | 10 +++---- .../en/reference/polynomial_rings/index.rst | 6 ++-- .../polynomial_rings/invariant_theory.rst | 2 +- .../polynomial_rings_multivar.rst | 2 +- .../polynomial_rings_toy_implementations.rst | 2 +- .../polynomial_rings_univar.rst | 2 +- src/doc/en/reference/power_series/index.rst | 2 +- src/doc/en/reference/probability/index.rst | 2 +- .../en/reference/quadratic_forms/index.rst | 2 +- src/doc/en/reference/quasimodfrm/index.rst | 2 +- src/doc/en/reference/quat_algebras/index.rst | 2 +- src/doc/en/reference/quivers/index.rst | 2 +- src/doc/en/reference/repl/index.rst | 24 +++++++-------- src/doc/en/reference/resolutions/index.rst | 2 +- .../reference/riemannian_geometry/index.rst | 2 +- src/doc/en/reference/rings/index.rst | 18 +++++------ .../en/reference/rings_numerical/index.rst | 6 ++-- src/doc/en/reference/rings_standard/index.rst | 4 +-- src/doc/en/reference/semirings/index.rst | 2 +- src/doc/en/reference/stats/index.rst | 2 +- .../tensor_free_modules/alt_forms.rst | 2 +- .../reference/tensor_free_modules/index.rst | 2 +- .../tensor_free_modules/morphisms.rst | 2 +- .../reference/tensor_free_modules/tensors.rst | 2 +- src/doc/en/reference/topology/index.rst | 2 +- src/doc/en/reference/valuations/index.rst | 6 ++-- src/sage_docbuild/conf.py | 18 +++++++++++ 89 files changed, 205 insertions(+), 188 deletions(-) diff --git a/src/doc/en/reference/algebras/cubic_hecke_algebra.rst b/src/doc/en/reference/algebras/cubic_hecke_algebra.rst index 5ba74eb00f0..94ca9bb27cc 100644 --- a/src/doc/en/reference/algebras/cubic_hecke_algebra.rst +++ b/src/doc/en/reference/algebras/cubic_hecke_algebra.rst @@ -2,7 +2,7 @@ Cubic Hecke Algebras ==================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/hecke_algebras/cubic_hecke_algebra sage/algebras/hecke_algebras/cubic_hecke_base_ring diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index 237343faf32..157426d9800 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -12,7 +12,7 @@ Free associative algebras and quotients --------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/free_algebra sage/algebras/free_algebra_element @@ -29,7 +29,7 @@ Finite dimensional algebras --------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element @@ -40,7 +40,7 @@ Named associative algebras -------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/affine_nil_temperley_lieb sage/algebras/askey_wilson @@ -73,7 +73,7 @@ Hecke algebras -------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/hecke_algebras/ariki_koike_algebra sage/algebras/iwahori_hecke_algebra @@ -85,7 +85,7 @@ Graded algebras --------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/finite_gca sage/algebras/commutative_dga @@ -94,7 +94,7 @@ Various associative algebras ---------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/associated_graded sage/algebras/cellular_basis @@ -106,7 +106,7 @@ Non-associative algebras ------------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 lie_algebras lie_conformal_algebras @@ -115,6 +115,6 @@ Non-associative algebras sage/combinat/free_prelie_algebra sage/algebras/shuffle_algebra sage/algebras/free_zinbiel_algebra - + .. include:: ../footer.txt diff --git a/src/doc/en/reference/algebras/lie_algebras.rst b/src/doc/en/reference/algebras/lie_algebras.rst index bc132fe88bd..23152ac0449 100644 --- a/src/doc/en/reference/algebras/lie_algebras.rst +++ b/src/doc/en/reference/algebras/lie_algebras.rst @@ -2,7 +2,7 @@ Lie Algebras ============ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/lie_algebras/abelian sage/algebras/lie_algebras/affine_lie_algebra diff --git a/src/doc/en/reference/algebras/quantum_groups.rst b/src/doc/en/reference/algebras/quantum_groups.rst index d89afc4b906..83a8e53cae1 100644 --- a/src/doc/en/reference/algebras/quantum_groups.rst +++ b/src/doc/en/reference/algebras/quantum_groups.rst @@ -2,7 +2,7 @@ Quantum Groups ============== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/quantum_groups/ace_quantum_onsager sage/algebras/quantum_groups/fock_space diff --git a/src/doc/en/reference/arithgroup/index.rst b/src/doc/en/reference/arithgroup/index.rst index c16f34d88fd..bb7f650320d 100644 --- a/src/doc/en/reference/arithgroup/index.rst +++ b/src/doc/en/reference/arithgroup/index.rst @@ -5,7 +5,7 @@ This chapter describes the basic functionality for finite index subgroups of the modular group `\SL_2(\ZZ)`. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/arithgroup/arithgroup_generic sage/modular/arithgroup/arithgroup_perm diff --git a/src/doc/en/reference/arithmetic_curves/index.rst b/src/doc/en/reference/arithmetic_curves/index.rst index 73f6f602490..40073920d61 100644 --- a/src/doc/en/reference/arithmetic_curves/index.rst +++ b/src/doc/en/reference/arithmetic_curves/index.rst @@ -2,7 +2,7 @@ Elliptic curves ========================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/elliptic_curves/constructor sage/schemes/elliptic_curves/jacobian @@ -15,7 +15,7 @@ Elliptic curves Maps between them .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/elliptic_curves/hom sage/schemes/elliptic_curves/weierstrass_morphism @@ -29,7 +29,7 @@ Elliptic curves over number fields ---------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/elliptic_curves/ell_rational_field sage/schemes/elliptic_curves/ec_database @@ -51,7 +51,7 @@ Elliptic curves over number fields The following relate to elliptic curves over local nonarchimedean fields. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/elliptic_curves/ell_local_data sage/schemes/elliptic_curves/kodaira_symbol @@ -60,7 +60,7 @@ The following relate to elliptic curves over local nonarchimedean fields. Analytic properties over `\CC`. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/elliptic_curves/ell_wp sage/schemes/elliptic_curves/period_lattice @@ -69,7 +69,7 @@ Analytic properties over `\CC`. Modularity and `L`-series over `\QQ`. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/elliptic_curves/modular_parametrization sage/schemes/elliptic_curves/ell_modular_symbols @@ -82,7 +82,7 @@ To be sorted ------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/elliptic_curves/descent_two_isogeny sage/schemes/elliptic_curves/ell_egros @@ -98,7 +98,7 @@ Hyperelliptic curves ==================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/hyperelliptic_curves/constructor sage/schemes/hyperelliptic_curves/hyperelliptic_generic diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index f45e9026068..a40cca76e0f 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -5,7 +5,7 @@ Introduction ------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/all @@ -13,7 +13,7 @@ The Sage Category Framework --------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/primer sage/categories/category @@ -25,7 +25,7 @@ Maps and Morphisms ------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/map sage/categories/homset @@ -36,7 +36,7 @@ Individual Categories --------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/action sage/categories/additive_groups @@ -206,7 +206,7 @@ Technical Categories ~~~~~~~~~~~~~~~~~~~~ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/facade_sets @@ -214,7 +214,7 @@ Functorial constructions ------------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/covariant_functorial_construction @@ -239,7 +239,7 @@ Examples of parents using categories ------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/examples/algebras_with_basis sage/categories/examples/commutative_additive_monoids @@ -276,7 +276,7 @@ Internals --------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/categories/category_types sage/categories/category_singleton diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index abba3458380..637d1a2e65f 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -95,7 +95,7 @@ is from a special code family, the derived codes inherit structural properties like decoding radius or minimum distance: .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/coding/subfield_subcode sage/coding/punctured_code @@ -127,7 +127,7 @@ Automorphism Groups of Linear Codes ----------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/coding/codecan/codecan sage/coding/codecan/autgroup_can_label @@ -136,7 +136,7 @@ Bounds for Parameters of Linear Codes ------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/coding/code_bounds sage/coding/delsarte_bounds @@ -145,7 +145,7 @@ Databases for Coding Theory --------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/coding/databases sage/coding/two_weight_db diff --git a/src/doc/en/reference/coercion/index.rst b/src/doc/en/reference/coercion/index.rst index d8d7c2ade79..d6d117cd263 100644 --- a/src/doc/en/reference/coercion/index.rst +++ b/src/doc/en/reference/coercion/index.rst @@ -671,7 +671,7 @@ Modules ------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/structure/coerce sage/structure/coerce_actions diff --git a/src/doc/en/reference/constants/index.rst b/src/doc/en/reference/constants/index.rst index 96ae5e256ae..b64ddc46a98 100644 --- a/src/doc/en/reference/constants/index.rst +++ b/src/doc/en/reference/constants/index.rst @@ -2,7 +2,7 @@ Constants ========= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/symbolic/constants diff --git a/src/doc/en/reference/cpython/index.rst b/src/doc/en/reference/cpython/index.rst index dce1253a535..5cb77b05605 100644 --- a/src/doc/en/reference/cpython/index.rst +++ b/src/doc/en/reference/cpython/index.rst @@ -5,7 +5,7 @@ SageMath has various modules to provide access to low-level Python internals. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/cpython/atexit sage/cpython/string diff --git a/src/doc/en/reference/cryptography/index.rst b/src/doc/en/reference/cryptography/index.rst index b2d628ab796..3c7997247be 100644 --- a/src/doc/en/reference/cryptography/index.rst +++ b/src/doc/en/reference/cryptography/index.rst @@ -2,7 +2,7 @@ Cryptography ============ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/crypto/cryptosystem diff --git a/src/doc/en/reference/curves/index.rst b/src/doc/en/reference/curves/index.rst index af3aad831d4..b95ab97f839 100644 --- a/src/doc/en/reference/curves/index.rst +++ b/src/doc/en/reference/curves/index.rst @@ -17,7 +17,7 @@ Plane conics ============ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/plane_conics/constructor sage/schemes/plane_conics/con_field @@ -30,7 +30,7 @@ Plane quartics ========================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/plane_quartics/quartic_constructor sage/schemes/plane_quartics/quartic_generic @@ -39,7 +39,7 @@ Riemann surfaces ================ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/schemes/riemann_surfaces/riemann_surface diff --git a/src/doc/en/reference/data_structures/index.rst b/src/doc/en/reference/data_structures/index.rst index 3cecb0fcfda..08c03313ad3 100644 --- a/src/doc/en/reference/data_structures/index.rst +++ b/src/doc/en/reference/data_structures/index.rst @@ -2,7 +2,7 @@ Data Structures =============== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/misc/binary_tree sage/data_structures/bitset diff --git a/src/doc/en/reference/diophantine_approximation/index.rst b/src/doc/en/reference/diophantine_approximation/index.rst index a1c49919f81..06a90c647aa 100644 --- a/src/doc/en/reference/diophantine_approximation/index.rst +++ b/src/doc/en/reference/diophantine_approximation/index.rst @@ -6,7 +6,7 @@ The diophantine approximation deals with the approximation of real numbers See the article :wikipedia:`Diophantine_approximation` for more information. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/continued_fraction diff --git a/src/doc/en/reference/doctest/index.rst b/src/doc/en/reference/doctest/index.rst index 88ec7a54e90..e224d33e4ae 100644 --- a/src/doc/en/reference/doctest/index.rst +++ b/src/doc/en/reference/doctest/index.rst @@ -2,7 +2,7 @@ Sage's Doctesting Framework =========================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/doctest/control sage/doctest/sources diff --git a/src/doc/en/reference/dynamics/cellular_automata.rst b/src/doc/en/reference/dynamics/cellular_automata.rst index 6197e127e23..846c5e91677 100644 --- a/src/doc/en/reference/dynamics/cellular_automata.rst +++ b/src/doc/en/reference/dynamics/cellular_automata.rst @@ -2,7 +2,7 @@ Cellular Automata ================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 ../sage/dynamics/cellular_automata/catalog diff --git a/src/doc/en/reference/dynamics/complex_dynamics.rst b/src/doc/en/reference/dynamics/complex_dynamics.rst index 06496b4d8a0..4ca4066dffd 100644 --- a/src/doc/en/reference/dynamics/complex_dynamics.rst +++ b/src/doc/en/reference/dynamics/complex_dynamics.rst @@ -2,6 +2,6 @@ Plotting of Mandelbrot and Julia Sets ======================================================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 ../sage/dynamics/complex_dynamics/mandel_julia diff --git a/src/doc/en/reference/dynamics/index.rst b/src/doc/en/reference/dynamics/index.rst index badd1b5cf3a..83cb7782f3a 100644 --- a/src/doc/en/reference/dynamics/index.rst +++ b/src/doc/en/reference/dynamics/index.rst @@ -4,7 +4,7 @@ Discrete dynamics ================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 cellular_automata complex_dynamics @@ -21,7 +21,7 @@ Arithmetic Dynamical Systems ---------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/dynamics/arithmetic_dynamics/generic_ds sage/dynamics/arithmetic_dynamics/affine_ds diff --git a/src/doc/en/reference/euclidean_spaces/index.rst b/src/doc/en/reference/euclidean_spaces/index.rst index 948458201c0..2f5ce8384d7 100644 --- a/src/doc/en/reference/euclidean_spaces/index.rst +++ b/src/doc/en/reference/euclidean_spaces/index.rst @@ -4,8 +4,7 @@ Euclidean Spaces and Vector Calculus ==================================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/examples/euclidean - sage/manifolds/operators diff --git a/src/doc/en/reference/finance/index.rst b/src/doc/en/reference/finance/index.rst index bba570a13ae..2c0f62cbf3c 100644 --- a/src/doc/en/reference/finance/index.rst +++ b/src/doc/en/reference/finance/index.rst @@ -2,7 +2,7 @@ Quantitative Finance ====================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/finance/stock sage/finance/option diff --git a/src/doc/en/reference/finite_rings/index.rst b/src/doc/en/reference/finite_rings/index.rst index dddbd66a54f..02e7f69ed8b 100644 --- a/src/doc/en/reference/finite_rings/index.rst +++ b/src/doc/en/reference/finite_rings/index.rst @@ -5,7 +5,7 @@ Finite Rings ------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/finite_rings/integer_mod_ring sage/rings/finite_rings/integer_mod @@ -14,7 +14,7 @@ Finite Fields ------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/finite_rings/finite_field_constructor sage/rings/finite_rings/finite_field_base @@ -26,7 +26,7 @@ Prime Fields ------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/finite_rings/finite_field_prime_modn sage/rings/finite_rings/hom_prime_finite_field @@ -35,7 +35,7 @@ Finite Fields Using Pari ------------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/finite_rings/finite_field_pari_ffelt sage/rings/finite_rings/element_pari_ffelt @@ -44,7 +44,7 @@ Finite Fields Using Givaro -------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/finite_rings/finite_field_givaro sage/rings/finite_rings/element_givaro @@ -54,7 +54,7 @@ Finite Fields of Characteristic 2 Using NTL ------------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/finite_rings/finite_field_ntl_gf2e sage/rings/finite_rings/element_ntl_gf2e @@ -63,7 +63,7 @@ Miscellaneous ------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/finite_rings/residue_field sage/rings/algebraic_closure_finite_field diff --git a/src/doc/en/reference/functions/index.rst b/src/doc/en/reference/functions/index.rst index 54769b6a95e..3916eb0c260 100644 --- a/src/doc/en/reference/functions/index.rst +++ b/src/doc/en/reference/functions/index.rst @@ -8,7 +8,7 @@ Built-in Functions ================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/functions/log sage/functions/trig diff --git a/src/doc/en/reference/game_theory/index.rst b/src/doc/en/reference/game_theory/index.rst index 1bee7a0d46d..eb7dab7193c 100644 --- a/src/doc/en/reference/game_theory/index.rst +++ b/src/doc/en/reference/game_theory/index.rst @@ -2,7 +2,7 @@ Game Theory =========== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/game_theory/cooperative_game sage/game_theory/matching_game diff --git a/src/doc/en/reference/games/index.rst b/src/doc/en/reference/games/index.rst index 8511337bb5d..12a7647655b 100644 --- a/src/doc/en/reference/games/index.rst +++ b/src/doc/en/reference/games/index.rst @@ -6,7 +6,7 @@ Rubik's cube solver (see `Rubik's Cube Group <../groups/sage/groups/perm_gps/cubegroup.html>`_). .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/games/sudoku sage/games/quantumino diff --git a/src/doc/en/reference/groups/index.rst b/src/doc/en/reference/groups/index.rst index dd0de5ba2f6..09d05d78aff 100644 --- a/src/doc/en/reference/groups/index.rst +++ b/src/doc/en/reference/groups/index.rst @@ -2,7 +2,7 @@ Groups ====== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/groups/groups_catalog sage/groups/group @@ -31,7 +31,7 @@ Abelian Groups -------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/groups/abelian_gps/abelian_group sage/groups/abelian_gps/abelian_group_gap @@ -51,7 +51,7 @@ Permutation Groups ------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/groups/perm_gps/permutation_groups_catalog sage/groups/perm_gps/constructor @@ -66,7 +66,7 @@ Matrix and Affine Groups ------------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/groups/matrix_gps/catalog sage/groups/matrix_gps/matrix_group @@ -90,7 +90,7 @@ Lie Groups ------------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/groups/lie_gps/nilpotent_lie_group @@ -98,7 +98,7 @@ Partition Refinement -------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/groups/perm_gps/partn_ref/canonical_augmentation sage/groups/perm_gps/partn_ref/data_structures @@ -110,7 +110,7 @@ Internals --------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/groups/matrix_gps/named_group diff --git a/src/doc/en/reference/hecke/index.rst b/src/doc/en/reference/hecke/index.rst index 4726b4e1cc9..24f18d9c662 100644 --- a/src/doc/en/reference/hecke/index.rst +++ b/src/doc/en/reference/hecke/index.rst @@ -8,7 +8,7 @@ Symbols <../modsym/index.html>`_ and `Modular Forms <../modfrm/index.html>`_. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/hecke/module sage/modular/hecke/ambient_module diff --git a/src/doc/en/reference/homology/index.rst b/src/doc/en/reference/homology/index.rst index 32424286980..8188233a52b 100644 --- a/src/doc/en/reference/homology/index.rst +++ b/src/doc/en/reference/homology/index.rst @@ -5,7 +5,7 @@ Sage includes some tools for algebraic topology, and in particular computing homology groups. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/homology/chain_complex sage/homology/chains diff --git a/src/doc/en/reference/interfaces/index.rst b/src/doc/en/reference/interfaces/index.rst index d007f6778f3..a2df80ff4b3 100644 --- a/src/doc/en/reference/interfaces/index.rst +++ b/src/doc/en/reference/interfaces/index.rst @@ -58,7 +58,7 @@ exact actual program available (especially useful for tab completion and testing to make sure nothing funny is going on). .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/interfaces/interface sage/interfaces/expect diff --git a/src/doc/en/reference/knots/index.rst b/src/doc/en/reference/knots/index.rst index 8fc794b4705..ed42964e5a5 100644 --- a/src/doc/en/reference/knots/index.rst +++ b/src/doc/en/reference/knots/index.rst @@ -2,7 +2,7 @@ Knot Theory =========== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/knots/knot sage/knots/link diff --git a/src/doc/en/reference/lfunctions/index.rst b/src/doc/en/reference/lfunctions/index.rst index 03238db6534..89487b344a9 100644 --- a/src/doc/en/reference/lfunctions/index.rst +++ b/src/doc/en/reference/lfunctions/index.rst @@ -5,7 +5,7 @@ Sage includes several standard open source packages for computing with :math:`L`-functions. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/lfunctions/lcalc sage/lfunctions/sympow diff --git a/src/doc/en/reference/libs/index.rst b/src/doc/en/reference/libs/index.rst index 43da10f063c..3b58fb289da 100644 --- a/src/doc/en/reference/libs/index.rst +++ b/src/doc/en/reference/libs/index.rst @@ -21,14 +21,14 @@ to be aware of the modules described in this chapter. ECL --- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/ecl eclib ----- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/eclib/interface sage/libs/eclib/mwrank @@ -40,7 +40,7 @@ eclib FLINT ----- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/flint/flint sage/libs/flint/fmpz_poly @@ -49,35 +49,35 @@ FLINT Giac ---- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/giac GMP-ECM ------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/libecm GSL --- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/gsl/array lcalc ----- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/lcalc/lcalc_Lfunction libSingular ----------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/singular/function sage/libs/singular/function_factory @@ -90,7 +90,7 @@ libSingular GAP --- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/gap/context_managers sage/libs/gap/gap_functions @@ -104,35 +104,35 @@ GAP LinBox ------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/linbox/linbox_flint_interface lrcalc ------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/lrcalc/lrcalc mpmath ------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/mpmath/utils NTL --- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/ntl/all PARI ---- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/pari sage/libs/pari/convert_sage @@ -141,7 +141,7 @@ PARI Symmetrica ---------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/libs/symmetrica/symmetrica diff --git a/src/doc/en/reference/logic/index.rst b/src/doc/en/reference/logic/index.rst index 8628a5e5bbd..bb914195f9b 100644 --- a/src/doc/en/reference/logic/index.rst +++ b/src/doc/en/reference/logic/index.rst @@ -2,7 +2,7 @@ Symbolic Logic ============== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/logic/propcalc sage/logic/boolformula diff --git a/src/doc/en/reference/manifolds/chart.rst b/src/doc/en/reference/manifolds/chart.rst index d9f38e2fee1..8cd42210dd7 100644 --- a/src/doc/en/reference/manifolds/chart.rst +++ b/src/doc/en/reference/manifolds/chart.rst @@ -2,7 +2,7 @@ Coordinate Charts ================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/chart diff --git a/src/doc/en/reference/manifolds/continuous_map.rst b/src/doc/en/reference/manifolds/continuous_map.rst index bd17639a2f4..59c6180099f 100644 --- a/src/doc/en/reference/manifolds/continuous_map.rst +++ b/src/doc/en/reference/manifolds/continuous_map.rst @@ -2,7 +2,7 @@ Continuous Maps =============== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/manifold_homset diff --git a/src/doc/en/reference/manifolds/degenerate_metric.rst b/src/doc/en/reference/manifolds/degenerate_metric.rst index c4f1ca4f415..2af47655f3a 100644 --- a/src/doc/en/reference/manifolds/degenerate_metric.rst +++ b/src/doc/en/reference/manifolds/degenerate_metric.rst @@ -2,7 +2,7 @@ Degenerate Metric Manifolds =========================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/degenerate diff --git a/src/doc/en/reference/manifolds/diff_form.rst b/src/doc/en/reference/manifolds/diff_form.rst index 4ce40bde2ab..e042770f5bd 100644 --- a/src/doc/en/reference/manifolds/diff_form.rst +++ b/src/doc/en/reference/manifolds/diff_form.rst @@ -2,7 +2,7 @@ Differential Forms ================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/diff_form_module diff --git a/src/doc/en/reference/manifolds/diff_manifold.rst b/src/doc/en/reference/manifolds/diff_manifold.rst index 31e86015d97..2edb2aee20f 100644 --- a/src/doc/en/reference/manifolds/diff_manifold.rst +++ b/src/doc/en/reference/manifolds/diff_manifold.rst @@ -2,7 +2,7 @@ Differentiable Manifolds ======================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/manifold diff --git a/src/doc/en/reference/manifolds/diff_map.rst b/src/doc/en/reference/manifolds/diff_map.rst index 385248c0506..11177f6004d 100644 --- a/src/doc/en/reference/manifolds/diff_map.rst +++ b/src/doc/en/reference/manifolds/diff_map.rst @@ -2,12 +2,12 @@ Differentiable Maps and Curves ============================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/manifold_homset sage/manifolds/differentiable/diff_map sage/manifolds/differentiable/curve - + sage/manifolds/differentiable/integrated_curve diff --git a/src/doc/en/reference/manifolds/diff_scalarfield.rst b/src/doc/en/reference/manifolds/diff_scalarfield.rst index 9eb217c7547..f1d86cbd221 100644 --- a/src/doc/en/reference/manifolds/diff_scalarfield.rst +++ b/src/doc/en/reference/manifolds/diff_scalarfield.rst @@ -2,7 +2,7 @@ Scalar Fields ============= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/scalarfield_algebra diff --git a/src/doc/en/reference/manifolds/diff_vector_bundle.rst b/src/doc/en/reference/manifolds/diff_vector_bundle.rst index fdfdf39df61..d237f8ffca6 100644 --- a/src/doc/en/reference/manifolds/diff_vector_bundle.rst +++ b/src/doc/en/reference/manifolds/diff_vector_bundle.rst @@ -2,7 +2,7 @@ Differentiable Vector Bundles ============================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/vector_bundle diff --git a/src/doc/en/reference/manifolds/euclidean_space.rst b/src/doc/en/reference/manifolds/euclidean_space.rst index 6ee1c2c820f..45c592d39a8 100644 --- a/src/doc/en/reference/manifolds/euclidean_space.rst +++ b/src/doc/en/reference/manifolds/euclidean_space.rst @@ -4,7 +4,7 @@ Euclidean Spaces and Vector Calculus ==================================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/examples/euclidean diff --git a/src/doc/en/reference/manifolds/manifold.rst b/src/doc/en/reference/manifolds/manifold.rst index 1384b507bf3..055ccebca68 100644 --- a/src/doc/en/reference/manifolds/manifold.rst +++ b/src/doc/en/reference/manifolds/manifold.rst @@ -2,7 +2,7 @@ Topological Manifolds ===================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/manifold diff --git a/src/doc/en/reference/manifolds/mixed_form.rst b/src/doc/en/reference/manifolds/mixed_form.rst index 0afbec4f527..51c98b61f15 100644 --- a/src/doc/en/reference/manifolds/mixed_form.rst +++ b/src/doc/en/reference/manifolds/mixed_form.rst @@ -2,7 +2,7 @@ Mixed Differential Forms ======================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/mixed_form_algebra diff --git a/src/doc/en/reference/manifolds/multivector.rst b/src/doc/en/reference/manifolds/multivector.rst index fe2c90815cb..1199ea1fdad 100644 --- a/src/doc/en/reference/manifolds/multivector.rst +++ b/src/doc/en/reference/manifolds/multivector.rst @@ -2,7 +2,7 @@ Alternating Multivector Fields ============================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/multivector_module diff --git a/src/doc/en/reference/manifolds/scalarfield.rst b/src/doc/en/reference/manifolds/scalarfield.rst index 3ba7a2e1e4e..62100d7feb3 100644 --- a/src/doc/en/reference/manifolds/scalarfield.rst +++ b/src/doc/en/reference/manifolds/scalarfield.rst @@ -2,7 +2,7 @@ Scalar Fields ============= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/scalarfield_algebra diff --git a/src/doc/en/reference/manifolds/tangent_space.rst b/src/doc/en/reference/manifolds/tangent_space.rst index 6c9e17899b5..8e55de2dbf6 100644 --- a/src/doc/en/reference/manifolds/tangent_space.rst +++ b/src/doc/en/reference/manifolds/tangent_space.rst @@ -2,7 +2,7 @@ Tangent Spaces ============== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/tangent_space diff --git a/src/doc/en/reference/manifolds/tensorfield.rst b/src/doc/en/reference/manifolds/tensorfield.rst index 6710757d5df..cf70207f185 100644 --- a/src/doc/en/reference/manifolds/tensorfield.rst +++ b/src/doc/en/reference/manifolds/tensorfield.rst @@ -2,7 +2,7 @@ Tensor Fields ============= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/tensorfield_module diff --git a/src/doc/en/reference/manifolds/vector_bundle.rst b/src/doc/en/reference/manifolds/vector_bundle.rst index 85905cbec94..f52b203609e 100644 --- a/src/doc/en/reference/manifolds/vector_bundle.rst +++ b/src/doc/en/reference/manifolds/vector_bundle.rst @@ -2,7 +2,7 @@ Topological Vector Bundles ========================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/vector_bundle diff --git a/src/doc/en/reference/manifolds/vectorfield.rst b/src/doc/en/reference/manifolds/vectorfield.rst index df9817539df..d489df1669a 100644 --- a/src/doc/en/reference/manifolds/vectorfield.rst +++ b/src/doc/en/reference/manifolds/vectorfield.rst @@ -2,7 +2,7 @@ Vector Fields ============= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/manifolds/differentiable/vectorfield_module diff --git a/src/doc/en/reference/matrices/index.rst b/src/doc/en/reference/matrices/index.rst index 6dc2a83a821..25aed6c2cb3 100644 --- a/src/doc/en/reference/matrices/index.rst +++ b/src/doc/en/reference/matrices/index.rst @@ -43,7 +43,7 @@ Finally, this module contains some data-structures for matrix-like objects like operation tables (e.g. the multiplication table of a group). .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/matrix/matrix_space diff --git a/src/doc/en/reference/modabvar/index.rst b/src/doc/en/reference/modabvar/index.rst index 768ede8e1f4..efad7a2c386 100644 --- a/src/doc/en/reference/modabvar/index.rst +++ b/src/doc/en/reference/modabvar/index.rst @@ -2,7 +2,7 @@ Modular Abelian Varieties ========================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/abvar/constructor sage/modular/abvar/abvar diff --git a/src/doc/en/reference/modfrm/index.rst b/src/doc/en/reference/modfrm/index.rst index c4b3cfa9c09..51b6c2cbb59 100644 --- a/src/doc/en/reference/modfrm/index.rst +++ b/src/doc/en/reference/modfrm/index.rst @@ -5,7 +5,7 @@ Module List ----------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/modform/constructor sage/modular/modform/space @@ -32,7 +32,7 @@ Design Notes ------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/modform/notes diff --git a/src/doc/en/reference/modfrm_hecketriangle/index.rst b/src/doc/en/reference/modfrm_hecketriangle/index.rst index 32368bb5aaa..2f783b529e1 100644 --- a/src/doc/en/reference/modfrm_hecketriangle/index.rst +++ b/src/doc/en/reference/modfrm_hecketriangle/index.rst @@ -3,7 +3,7 @@ Modular Forms for Hecke Triangle Groups .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/modform_hecketriangle/readme diff --git a/src/doc/en/reference/modmisc/index.rst b/src/doc/en/reference/modmisc/index.rst index a465c78c3ad..191fe5fbefc 100644 --- a/src/doc/en/reference/modmisc/index.rst +++ b/src/doc/en/reference/modmisc/index.rst @@ -2,7 +2,7 @@ Miscellaneous Modular-Form-Related Modules ========================================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/dirichlet sage/modular/cusps diff --git a/src/doc/en/reference/modsym/index.rst b/src/doc/en/reference/modsym/index.rst index d23bf9b0224..e5b54be0644 100644 --- a/src/doc/en/reference/modsym/index.rst +++ b/src/doc/en/reference/modsym/index.rst @@ -2,7 +2,7 @@ Modular Symbols =============== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/modsym/modsym @@ -36,7 +36,7 @@ Overconvergent modular symbols ---------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/pollack_stevens/space sage/modular/pollack_stevens/distributions diff --git a/src/doc/en/reference/modules/index.rst b/src/doc/en/reference/modules/index.rst index 643a669a6b0..c6109643c35 100644 --- a/src/doc/en/reference/modules/index.rst +++ b/src/doc/en/reference/modules/index.rst @@ -4,7 +4,7 @@ Modules Sage provides modules of various kinds over various base rings. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/tutorial_free_modules @@ -13,7 +13,7 @@ Free modules, submodules, and quotients --------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/module sage/modules/free_module @@ -27,7 +27,7 @@ Modules with basis ------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/with_basis/__init__ sage/modules/with_basis/cell_module @@ -41,7 +41,7 @@ Finitely generated modules over a PID ------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/fg_pid/fgp_module sage/modules/fg_pid/fgp_element @@ -51,7 +51,7 @@ Finitely presented graded modules --------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/fp_graded/free_module sage/modules/fp_graded/free_element @@ -69,7 +69,7 @@ Special modules --------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/free_module_integer sage/modules/free_quadratic_module @@ -82,7 +82,7 @@ Morphisms --------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/vector_space_homspace sage/modules/vector_space_morphism @@ -96,7 +96,7 @@ Vectors ------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/vector_integer_dense sage/modules/vector_mod2_dense @@ -114,7 +114,7 @@ Misc ---- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modules/diamond_cutting sage/modules/tensor_operations diff --git a/src/doc/en/reference/monoids/index.rst b/src/doc/en/reference/monoids/index.rst index 0e7279db6d3..fa394698b28 100644 --- a/src/doc/en/reference/monoids/index.rst +++ b/src/doc/en/reference/monoids/index.rst @@ -6,7 +6,7 @@ finite number of indeterminates, as well as free partially commutative monoids (trace monoids). .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/monoids/monoid sage/monoids/free_monoid diff --git a/src/doc/en/reference/noncommutative_polynomial_rings/index.rst b/src/doc/en/reference/noncommutative_polynomial_rings/index.rst index 9848988d126..dca4c3fe931 100644 --- a/src/doc/en/reference/noncommutative_polynomial_rings/index.rst +++ b/src/doc/en/reference/noncommutative_polynomial_rings/index.rst @@ -5,7 +5,7 @@ Univariate Ore polynomial rings ------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/polynomial/ore_polynomial_ring sage/rings/polynomial/ore_polynomial_element @@ -18,7 +18,7 @@ Fraction field of Ore polynomial rings -------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/polynomial/ore_function_field sage/rings/polynomial/ore_function_element diff --git a/src/doc/en/reference/padics/index.rst b/src/doc/en/reference/padics/index.rst index 68b68bd3922..6efcee751a6 100644 --- a/src/doc/en/reference/padics/index.rst +++ b/src/doc/en/reference/padics/index.rst @@ -2,7 +2,7 @@ ======================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/padics/tutorial diff --git a/src/doc/en/reference/parallel/index.rst b/src/doc/en/reference/parallel/index.rst index 6900e061a94..13b16a23a53 100644 --- a/src/doc/en/reference/parallel/index.rst +++ b/src/doc/en/reference/parallel/index.rst @@ -2,7 +2,7 @@ Parallel Computing ================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/parallel/decorate sage/parallel/reference diff --git a/src/doc/en/reference/plot3d/index.rst b/src/doc/en/reference/plot3d/index.rst index 2cbabe1b75c..cf8ab828276 100644 --- a/src/doc/en/reference/plot3d/index.rst +++ b/src/doc/en/reference/plot3d/index.rst @@ -2,7 +2,7 @@ =========== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/plot3d/introduction @@ -10,7 +10,7 @@ Function and Data Plots ----------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/plot3d/plot3d sage/plot/plot3d/parametric_plot3d @@ -24,7 +24,7 @@ Basic Shapes and Primitives --------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/plot3d/base sage/plot/plot3d/shapes @@ -37,7 +37,7 @@ Infrastructure -------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/plot3d/texture sage/plot/plot3d/index_face_set @@ -48,7 +48,7 @@ Backends -------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/plot3d/tachyon threejs diff --git a/src/doc/en/reference/plotting/index.rst b/src/doc/en/reference/plotting/index.rst index 662c5678481..1c1c4a03b17 100644 --- a/src/doc/en/reference/plotting/index.rst +++ b/src/doc/en/reference/plotting/index.rst @@ -5,7 +5,7 @@ General ------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/plot sage/plot/text @@ -16,7 +16,7 @@ Function and Data Plots ----------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/complex_plot sage/plot/contour_plot @@ -32,7 +32,7 @@ Plots of Other Mathematical Objects ----------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/graphs/graph_plot sage/plot/matrix_plot @@ -41,7 +41,7 @@ Basic Shapes ------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/arc sage/plot/arrow @@ -60,7 +60,7 @@ Infrastructure and Low-Level Functions -------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/plot/graphics sage/plot/multigraphics diff --git a/src/doc/en/reference/polynomial_rings/index.rst b/src/doc/en/reference/polynomial_rings/index.rst index bbdf14685ca..8a8ef70563c 100644 --- a/src/doc/en/reference/polynomial_rings/index.rst +++ b/src/doc/en/reference/polynomial_rings/index.rst @@ -5,7 +5,7 @@ Polynomial Rings ---------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/polynomial/polynomial_ring_constructor @@ -13,7 +13,7 @@ Univariate Polynomials ---------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 polynomial_rings_univar sage/rings/polynomial/convolution @@ -23,7 +23,7 @@ Multivariate Polynomials ------------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 polynomial_rings_multivar invariant_theory diff --git a/src/doc/en/reference/polynomial_rings/invariant_theory.rst b/src/doc/en/reference/polynomial_rings/invariant_theory.rst index e57e1226eed..48cca8f9cd0 100644 --- a/src/doc/en/reference/polynomial_rings/invariant_theory.rst +++ b/src/doc/en/reference/polynomial_rings/invariant_theory.rst @@ -3,7 +3,7 @@ Classical Invariant Theory ========================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/invariants/invariant_theory sage/rings/invariants/reconstruction diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst index 6147929ccf2..9a2ebf494ae 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst @@ -20,7 +20,7 @@ are implemented using the PolyBoRi library (cf. :mod:`sage.rings.polynomial.pbor .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/polynomial/term_order diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_toy_implementations.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_toy_implementations.rst index 1a27c451f23..baa14664c08 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_toy_implementations.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_toy_implementations.rst @@ -3,7 +3,7 @@ Educational Versions of Groebner Basis Related Algorithms ========================================================= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/polynomial/toy_buchberger sage/rings/polynomial/toy_variety diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst index bd55ed8b014..414c04bb611 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst @@ -12,7 +12,7 @@ than pure Python classes and thus can only inherit from a single base class, whereas others have multiple bases. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/polynomial/polynomial_ring sage/rings/polynomial/polynomial_ring_homomorphism diff --git a/src/doc/en/reference/power_series/index.rst b/src/doc/en/reference/power_series/index.rst index dba377477f1..6ab1c2004e3 100644 --- a/src/doc/en/reference/power_series/index.rst +++ b/src/doc/en/reference/power_series/index.rst @@ -2,7 +2,7 @@ Power Series Rings and Laurent Series Rings =========================================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/power_series_ring sage/rings/power_series_ring_element diff --git a/src/doc/en/reference/probability/index.rst b/src/doc/en/reference/probability/index.rst index 633219bddbf..da9318b3398 100644 --- a/src/doc/en/reference/probability/index.rst +++ b/src/doc/en/reference/probability/index.rst @@ -2,7 +2,7 @@ Probability =========== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/probability/probability_distribution sage/probability/random_variable diff --git a/src/doc/en/reference/quadratic_forms/index.rst b/src/doc/en/reference/quadratic_forms/index.rst index 65fb2563115..7169e7ac503 100644 --- a/src/doc/en/reference/quadratic_forms/index.rst +++ b/src/doc/en/reference/quadratic_forms/index.rst @@ -2,7 +2,7 @@ Quadratic Forms =============== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/quadratic_forms/quadratic_form sage/quadratic_forms/binary_qf diff --git a/src/doc/en/reference/quasimodfrm/index.rst b/src/doc/en/reference/quasimodfrm/index.rst index 4fb81ca671d..0d450ed9dc2 100644 --- a/src/doc/en/reference/quasimodfrm/index.rst +++ b/src/doc/en/reference/quasimodfrm/index.rst @@ -5,7 +5,7 @@ Module List ----------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/modular/quasimodform/ring sage/modular/quasimodform/element diff --git a/src/doc/en/reference/quat_algebras/index.rst b/src/doc/en/reference/quat_algebras/index.rst index 110d732342b..1cdef1b7cea 100644 --- a/src/doc/en/reference/quat_algebras/index.rst +++ b/src/doc/en/reference/quat_algebras/index.rst @@ -2,7 +2,7 @@ Quaternion Algebras =================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/algebras/quatalg/quaternion_algebra sage/algebras/quatalg/quaternion_algebra_element diff --git a/src/doc/en/reference/quivers/index.rst b/src/doc/en/reference/quivers/index.rst index b1e3aa48f87..98bf561c4dc 100644 --- a/src/doc/en/reference/quivers/index.rst +++ b/src/doc/en/reference/quivers/index.rst @@ -2,7 +2,7 @@ Quivers ======= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/quivers/algebra sage/quivers/algebra_elements diff --git a/src/doc/en/reference/repl/index.rst b/src/doc/en/reference/repl/index.rst index 21a2f285f1f..d12d8866dda 100644 --- a/src/doc/en/reference/repl/index.rst +++ b/src/doc/en/reference/repl/index.rst @@ -3,7 +3,7 @@ The Sage Command Line The Sage Read-Eval-Print-Loop (REPL) is based on IPython. In this document, you'll find how the IPython integration works. You should -also be familiar with the documentation for IPython. +also be familiar with the documentation for IPython. For more details about using the Sage command line, see the Sage tutorial. @@ -12,7 +12,7 @@ Running Sage ------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 options startup @@ -26,8 +26,8 @@ Sage commands are "preparsed" to valid Python syntax. This allows for example to support the ``R. = QQ[]`` syntax. .. toctree:: - :maxdepth: 2 - + :maxdepth: 1 + sage/repl/preparse @@ -39,8 +39,8 @@ in a Sage session. Attaching is similar, except that the attached file is reloaded whenever it is changed. .. toctree:: - :maxdepth: 2 - + :maxdepth: 1 + sage/repl/load sage/repl/attach @@ -53,18 +53,18 @@ printed. This again builds on how IPython formats output. Technically, this works using a modified displayhook in Python. .. toctree:: - :maxdepth: 2 - + :maxdepth: 1 + sage/repl/display/formatter sage/repl/display/pretty_print sage/repl/display/fancy_repr sage/repl/display/util - + Display Backend Infrastructure ------------------------------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/repl/rich_output/display_manager sage/repl/rich_output/preferences @@ -75,7 +75,7 @@ Display Backend Infrastructure sage/repl/rich_output/output_graphics3d sage/repl/rich_output/output_video sage/repl/rich_output/output_catalog - + sage/repl/rich_output/backend_base sage/repl/rich_output/test_backend sage/repl/rich_output/backend_doctest @@ -85,7 +85,7 @@ Miscellaneous ------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/repl/interpreter sage/repl/ipython_extension diff --git a/src/doc/en/reference/resolutions/index.rst b/src/doc/en/reference/resolutions/index.rst index 82f73cb94c8..861023c8aec 100644 --- a/src/doc/en/reference/resolutions/index.rst +++ b/src/doc/en/reference/resolutions/index.rst @@ -5,7 +5,7 @@ Free and graded resolutions are tools for commutative algebra and algebraic geometry. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/homology/free_resolution sage/homology/graded_resolution diff --git a/src/doc/en/reference/riemannian_geometry/index.rst b/src/doc/en/reference/riemannian_geometry/index.rst index caa6ea15685..5f16761f845 100644 --- a/src/doc/en/reference/riemannian_geometry/index.rst +++ b/src/doc/en/reference/riemannian_geometry/index.rst @@ -2,7 +2,7 @@ Differential Geometry of Curves and Surfaces ============================================ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/geometry/riemannian_manifolds/parametrized_surface3d sage/geometry/riemannian_manifolds/surface3d_generators diff --git a/src/doc/en/reference/rings/index.rst b/src/doc/en/reference/rings/index.rst index 58451740464..31e1b5285fc 100644 --- a/src/doc/en/reference/rings/index.rst +++ b/src/doc/en/reference/rings/index.rst @@ -5,7 +5,7 @@ Base Classes for Rings, Algebras and Fields ------------------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/ring @@ -13,7 +13,7 @@ Ideals ------ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/ideal sage/rings/ideal_monoid @@ -23,7 +23,7 @@ Ring Morphisms -------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/morphism sage/rings/homset @@ -32,7 +32,7 @@ Quotient Rings -------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/quotient_ring sage/rings/quotient_ring_element @@ -41,7 +41,7 @@ Fraction Fields --------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/fraction_field sage/rings/fraction_field_element @@ -50,7 +50,7 @@ Localization --------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/localization @@ -58,7 +58,7 @@ Ring Extensions --------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/ring_extension sage/rings/ring_extension_element @@ -69,7 +69,7 @@ Utilities --------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/big_oh sage/rings/infinity @@ -79,7 +79,7 @@ Derivation ---------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/derivation diff --git a/src/doc/en/reference/rings_numerical/index.rst b/src/doc/en/reference/rings_numerical/index.rst index 955c61beee6..a20066c2ec7 100644 --- a/src/doc/en/reference/rings_numerical/index.rst +++ b/src/doc/en/reference/rings_numerical/index.rst @@ -16,7 +16,7 @@ which builds on GMP. In many cases the PARI C-library is used to compute special functions when implementations aren't otherwise available. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/real_mpfr sage/rings/complex_mpfr @@ -32,7 +32,7 @@ Sage implements real and complex interval arithmetic using MPFI ComplexBallField). .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/real_mpfi sage/rings/real_interval_absolute @@ -46,7 +46,7 @@ Exact Real Arithmetic --------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/real_lazy diff --git a/src/doc/en/reference/rings_standard/index.rst b/src/doc/en/reference/rings_standard/index.rst index 922de453f59..4bb5338609f 100644 --- a/src/doc/en/reference/rings_standard/index.rst +++ b/src/doc/en/reference/rings_standard/index.rst @@ -5,7 +5,7 @@ Integers -------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/integer_ring sage/rings/integer @@ -30,7 +30,7 @@ Rationals --------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/rational_field sage/rings/rational diff --git a/src/doc/en/reference/semirings/index.rst b/src/doc/en/reference/semirings/index.rst index 6cab3c1421a..b40e71c54e1 100644 --- a/src/doc/en/reference/semirings/index.rst +++ b/src/doc/en/reference/semirings/index.rst @@ -2,7 +2,7 @@ Standard Semirings ================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/semirings/non_negative_integer_semiring sage/rings/semirings/tropical_semiring diff --git a/src/doc/en/reference/stats/index.rst b/src/doc/en/reference/stats/index.rst index e55454305c6..0020b113d66 100644 --- a/src/doc/en/reference/stats/index.rst +++ b/src/doc/en/reference/stats/index.rst @@ -2,7 +2,7 @@ Statistics ========== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/stats/basic_stats sage/stats/intlist diff --git a/src/doc/en/reference/tensor_free_modules/alt_forms.rst b/src/doc/en/reference/tensor_free_modules/alt_forms.rst index a6a2b87818d..c614c8fd531 100644 --- a/src/doc/en/reference/tensor_free_modules/alt_forms.rst +++ b/src/doc/en/reference/tensor_free_modules/alt_forms.rst @@ -2,7 +2,7 @@ Alternating tensors =================== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/tensor/modules/ext_pow_free_module diff --git a/src/doc/en/reference/tensor_free_modules/index.rst b/src/doc/en/reference/tensor_free_modules/index.rst index 2d2f7db3613..5e67a0e0389 100644 --- a/src/doc/en/reference/tensor_free_modules/index.rst +++ b/src/doc/en/reference/tensor_free_modules/index.rst @@ -9,7 +9,7 @@ not depend upon other SageManifolds classes. In other words, it constitutes a self-consistent subset that can be used independently of SageManifolds. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/tensor/modules/finite_rank_free_module diff --git a/src/doc/en/reference/tensor_free_modules/morphisms.rst b/src/doc/en/reference/tensor_free_modules/morphisms.rst index fa661c8978d..e05a1670861 100644 --- a/src/doc/en/reference/tensor_free_modules/morphisms.rst +++ b/src/doc/en/reference/tensor_free_modules/morphisms.rst @@ -2,7 +2,7 @@ Morphisms ========= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/tensor/modules/free_module_homset diff --git a/src/doc/en/reference/tensor_free_modules/tensors.rst b/src/doc/en/reference/tensor_free_modules/tensors.rst index 434ea734191..f7d6e61e34f 100644 --- a/src/doc/en/reference/tensor_free_modules/tensors.rst +++ b/src/doc/en/reference/tensor_free_modules/tensors.rst @@ -2,7 +2,7 @@ Tensors ======= .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/tensor/modules/tensor_free_module diff --git a/src/doc/en/reference/topology/index.rst b/src/doc/en/reference/topology/index.rst index 8550c2b0c42..1442b327ed2 100644 --- a/src/doc/en/reference/topology/index.rst +++ b/src/doc/en/reference/topology/index.rst @@ -8,7 +8,7 @@ also available, mainly for developers who want to use it as a base for other types of cell complexes. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/topology/simplicial_complex sage/topology/simplicial_complex_morphism diff --git a/src/doc/en/reference/valuations/index.rst b/src/doc/en/reference/valuations/index.rst index 67daac25902..90a8c3c97f7 100644 --- a/src/doc/en/reference/valuations/index.rst +++ b/src/doc/en/reference/valuations/index.rst @@ -46,7 +46,7 @@ Similarly, valuations can be defined on function fields:: sage: v = K.valuation(x) sage: v(1/x) -1 - + sage: v = K.valuation(1/x) sage: v(1/x) 1 @@ -88,7 +88,7 @@ polynomial the minimum of the coefficient valuations:: sage: R. = QQ[] sage: v = GaussValuation(R, valuations.TrivialValuation(QQ)) - + The Gauss valuation can be augmented by specifying that `x - 4` has valuation 1:: sage: v = v.augmentation(x - 4, 1); v @@ -184,7 +184,7 @@ More Details ============ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/rings/valuation/value_group sage/rings/valuation/valuation diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 4c880cdca63..4c00a1d338b 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -187,6 +187,24 @@ def sphinx_plot(graphics, **kwds): highlighting.lexers['ipython'] = IPyLexer() highlight_language = 'ipycon' +# Create table of contents entries for domain objects (e.g. functions, classes, +# attributes, etc.). Default is True. +toc_object_entries = True + +# A string that determines how domain objects (e.g. functions, classes, +# attributes, etc.) are displayed in their table of contents entry. +# +# Use "domain" to allow the domain to determine the appropriate number of parents +# to show. For example, the Python domain would show Class.method() and +# function(), leaving out the module. level of parents. This is the default +# setting. +# +# Use "hide" to only show the name of the element without any parents (i.e. method()). +# +# Use "all" to show the fully-qualified name for the object (i.e. module.Class.method()), +# displaying all parents. +toc_object_entries_show_parents = 'hide' + # Extension configuration # ----------------------- From aa2c315ba7ca755992c0e6b1278e560a70e75408 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:00:09 -0700 Subject: [PATCH 333/414] build/pkgs/ipython: Update to 8.6.0 --- build/pkgs/ipython/checksums.ini | 6 +++--- build/pkgs/ipython/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/ipython/checksums.ini b/build/pkgs/ipython/checksums.ini index ab40792a0a7..a539eb830b4 100644 --- a/build/pkgs/ipython/checksums.ini +++ b/build/pkgs/ipython/checksums.ini @@ -1,5 +1,5 @@ tarball=ipython-VERSION.tar.gz -sha1=98270c68edcfbcd02b3d76d9c1ab6030bbb92171 -md5=dc5d78f3d622b027a0f32f9a545b44ff -cksum=3907293880 +sha1=e0dd247f29befed1159d9bdca987d90c2ee0d34a +md5=8c98f6def0622ea32975cb779247c3d7 +cksum=2860792697 upstream_url=https://pypi.io/packages/source/i/ipython/ipython-VERSION.tar.gz diff --git a/build/pkgs/ipython/package-version.txt b/build/pkgs/ipython/package-version.txt index a2f28f43be3..acd405b1d62 100644 --- a/build/pkgs/ipython/package-version.txt +++ b/build/pkgs/ipython/package-version.txt @@ -1 +1 @@ -8.4.0 +8.6.0 From eb2173ce26a4a9d45699cbee92e995bd1bca2d27 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:06:31 -0700 Subject: [PATCH 334/414] build/pkgs/traitlets: Update to 5.5.0 --- build/pkgs/traitlets/checksums.ini | 6 +++--- build/pkgs/traitlets/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/traitlets/checksums.ini b/build/pkgs/traitlets/checksums.ini index fd4f1d895ed..4d90280ec63 100644 --- a/build/pkgs/traitlets/checksums.ini +++ b/build/pkgs/traitlets/checksums.ini @@ -1,5 +1,5 @@ tarball=traitlets-VERSION.tar.gz -sha1=80e1e820c169295ca66428e1414cf33c36c61386 -md5=ea9b920bd09fa8fcc44325c4d6a6800d -cksum=1563220888 +sha1=cb5c3545ddad62ee6700ea0f279b3d168ac58ed9 +md5=d5f87bbea8acf897ac3e435c7b71acdc +cksum=238091586 upstream_url=https://pypi.io/packages/source/t/traitlets/traitlets-VERSION.tar.gz diff --git a/build/pkgs/traitlets/package-version.txt b/build/pkgs/traitlets/package-version.txt index 03f488b076a..d50359de185 100644 --- a/build/pkgs/traitlets/package-version.txt +++ b/build/pkgs/traitlets/package-version.txt @@ -1 +1 @@ -5.3.0 +5.5.0 From c517afb637b3f9f60d8403886c82d712f6fd9026 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:07:19 -0700 Subject: [PATCH 335/414] build/pkgs/pygments: Update to 2.13.0 --- build/pkgs/pygments/checksums.ini | 6 +++--- build/pkgs/pygments/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pygments/checksums.ini b/build/pkgs/pygments/checksums.ini index f7b5fdbde3a..14fc1c04cdb 100644 --- a/build/pkgs/pygments/checksums.ini +++ b/build/pkgs/pygments/checksums.ini @@ -1,5 +1,5 @@ tarball=Pygments-VERSION.tar.gz -sha1=92e83541b557dad3f692d0d9d02733c2c842c836 -md5=2137c19d9ac0cc556badc89e746c0e62 -cksum=3384039979 +sha1=adaf31bf13a7bcc210568537138e0984ecdea626 +md5=6ccae578d28d18968b30a4711652fd9a +cksum=613387624 upstream_url=https://pypi.io/packages/source/p/pygments/Pygments-VERSION.tar.gz diff --git a/build/pkgs/pygments/package-version.txt b/build/pkgs/pygments/package-version.txt index d8b698973a4..fb2c0766b7c 100644 --- a/build/pkgs/pygments/package-version.txt +++ b/build/pkgs/pygments/package-version.txt @@ -1 +1 @@ -2.12.0 +2.13.0 From 07a9a33642e1e93c23acd5491f16a520767b159e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:12:05 -0700 Subject: [PATCH 336/414] build/pkgs/nbformat: Update to 5.7.0 --- build/pkgs/nbformat/checksums.ini | 6 +++--- build/pkgs/nbformat/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/nbformat/checksums.ini b/build/pkgs/nbformat/checksums.ini index 39cbb5ac27b..af04e4f35aa 100644 --- a/build/pkgs/nbformat/checksums.ini +++ b/build/pkgs/nbformat/checksums.ini @@ -1,5 +1,5 @@ tarball=nbformat-VERSION.tar.gz -sha1=77f321311289719867b958f40f58a570f3825c11 -md5=a11ccf44c2d984d1b8325a3463a9ae20 -cksum=3746246082 +sha1=ecad83c07bdc475f6fd88d28485cf8fe31fbba41 +md5=5e11cc3240d4b1410610786309cc6076 +cksum=767940068 upstream_url=https://pypi.io/packages/source/n/nbformat/nbformat-VERSION.tar.gz diff --git a/build/pkgs/nbformat/package-version.txt b/build/pkgs/nbformat/package-version.txt index 8a30e8f94a3..42cdd0b540f 100644 --- a/build/pkgs/nbformat/package-version.txt +++ b/build/pkgs/nbformat/package-version.txt @@ -1 +1 @@ -5.4.0 +5.7.0 From 5da03da6517633e1e07715c50b1339ca45b09f5a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:18:25 -0700 Subject: [PATCH 337/414] build/pkgs/hatch_nodejs_version: New (nbformat build dep) --- build/pkgs/hatch_nodejs_version/SPKG.rst | 18 ++++++++++++++++++ build/pkgs/hatch_nodejs_version/checksums.ini | 5 +++++ build/pkgs/hatch_nodejs_version/dependencies | 4 ++++ .../hatch_nodejs_version/install-requires.txt | 1 + .../hatch_nodejs_version/package-version.txt | 1 + .../pkgs/hatch_nodejs_version/spkg-install.in | 2 ++ build/pkgs/hatch_nodejs_version/type | 1 + build/pkgs/nbformat/dependencies | 2 +- 8 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/hatch_nodejs_version/SPKG.rst create mode 100644 build/pkgs/hatch_nodejs_version/checksums.ini create mode 100644 build/pkgs/hatch_nodejs_version/dependencies create mode 100644 build/pkgs/hatch_nodejs_version/install-requires.txt create mode 100644 build/pkgs/hatch_nodejs_version/package-version.txt create mode 100644 build/pkgs/hatch_nodejs_version/spkg-install.in create mode 100644 build/pkgs/hatch_nodejs_version/type diff --git a/build/pkgs/hatch_nodejs_version/SPKG.rst b/build/pkgs/hatch_nodejs_version/SPKG.rst new file mode 100644 index 00000000000..6a1cd5f991c --- /dev/null +++ b/build/pkgs/hatch_nodejs_version/SPKG.rst @@ -0,0 +1,18 @@ +hatch_nodejs_version: Hatch plugin for versioning from a package.json file +========================================================================== + +Description +----------- + +Hatch plugin for versioning from a package.json file + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/hatch-nodejs-version/ + diff --git a/build/pkgs/hatch_nodejs_version/checksums.ini b/build/pkgs/hatch_nodejs_version/checksums.ini new file mode 100644 index 00000000000..d5210b7eab0 --- /dev/null +++ b/build/pkgs/hatch_nodejs_version/checksums.ini @@ -0,0 +1,5 @@ +tarball=hatch_nodejs_version-VERSION.tar.gz +sha1=27420a3bae3f787640b2c8ad522d75fa32526294 +md5=644c2aea7558de91bbd66217fec6a1b9 +cksum=4058013444 +upstream_url=https://pypi.io/packages/source/h/hatch_nodejs_version/hatch_nodejs_version-VERSION.tar.gz diff --git a/build/pkgs/hatch_nodejs_version/dependencies b/build/pkgs/hatch_nodejs_version/dependencies new file mode 100644 index 00000000000..e9293f104ca --- /dev/null +++ b/build/pkgs/hatch_nodejs_version/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) hatchling | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/hatch_nodejs_version/install-requires.txt b/build/pkgs/hatch_nodejs_version/install-requires.txt new file mode 100644 index 00000000000..5c606fe80f5 --- /dev/null +++ b/build/pkgs/hatch_nodejs_version/install-requires.txt @@ -0,0 +1 @@ +hatch-nodejs-version diff --git a/build/pkgs/hatch_nodejs_version/package-version.txt b/build/pkgs/hatch_nodejs_version/package-version.txt new file mode 100644 index 00000000000..0d91a54c7d4 --- /dev/null +++ b/build/pkgs/hatch_nodejs_version/package-version.txt @@ -0,0 +1 @@ +0.3.0 diff --git a/build/pkgs/hatch_nodejs_version/spkg-install.in b/build/pkgs/hatch_nodejs_version/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/hatch_nodejs_version/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/hatch_nodejs_version/type b/build/pkgs/hatch_nodejs_version/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/hatch_nodejs_version/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/nbformat/dependencies b/build/pkgs/nbformat/dependencies index 258e07a9163..6c8921f1382 100644 --- a/build/pkgs/nbformat/dependencies +++ b/build/pkgs/nbformat/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) jsonschema fastjsonschema jupyter_core traitlets | $(PYTHON_TOOLCHAIN) +$(PYTHON) jsonschema fastjsonschema jupyter_core traitlets | $(PYTHON_TOOLCHAIN) hatchling hatch_nodejs_version ---------- All lines of this file are ignored except the first. From bfe5a2619cfb3f8bc2a93880f35f3ac446c7e008 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:29:21 -0700 Subject: [PATCH 338/414] build/pkgs/nbclient: Update to 0.7.0 --- build/pkgs/nbclient/checksums.ini | 6 +++--- build/pkgs/nbclient/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/nbclient/checksums.ini b/build/pkgs/nbclient/checksums.ini index de286fc5e1b..459341ff20e 100644 --- a/build/pkgs/nbclient/checksums.ini +++ b/build/pkgs/nbclient/checksums.ini @@ -1,5 +1,5 @@ tarball=nbclient-VERSION.tar.gz -sha1=e20fb7fc1fe8c421f3f7e8f749ee8fdc38c1811a -md5=ad28a33484bbd348ef04f142d32669f5 -cksum=2210542646 +sha1=8f6309bc37fb2c5b49fcdfe835b6ef9762fa583c +md5=6a59800791be74079cf2ade421526289 +cksum=1870577652 upstream_url=https://pypi.io/packages/source/n/nbclient/nbclient-VERSION.tar.gz diff --git a/build/pkgs/nbclient/package-version.txt b/build/pkgs/nbclient/package-version.txt index d2b13eb644d..faef31a4357 100644 --- a/build/pkgs/nbclient/package-version.txt +++ b/build/pkgs/nbclient/package-version.txt @@ -1 +1 @@ -0.6.4 +0.7.0 From de39745601c2e627f5c844310a7099fa055347de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 1 Nov 2022 08:32:34 +0100 Subject: [PATCH 339/414] fix wrong change --- src/sage/rings/number_field/number_field.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index ccafb7e20be..b670965e0c9 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -568,7 +568,7 @@ def NumberField(polynomial, name=None, check=True, names=None, embedding=None, raise TypeError("NumberField() got an unexpected keyword argument '%s'" % key) if not (val is None or isinstance(val, list) and all(c is None for c in val)): raise NotImplementedError("Number field with prescribed %s is not implemented" % key) - if isinstance(polynomial, (list,tuple)): + if isinstance(polynomial, (list, tuple)): return NumberFieldTower(polynomial, names=name, check=check, embeddings=embedding, latex_names=latex_name, assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes, structures=structure) return NumberField_version2(polynomial=polynomial, name=name, check=check, embedding=embedding, latex_name=latex_name, assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes, structure=structure) @@ -655,7 +655,7 @@ def create_key_and_extra_args(self, polynomial, name, check, embedding, latex_na polynomial = polynomial.change_ring(polynomial.base_ring().fraction_field()) # normalize embedding - if isinstance(embedding, (list,tuple)): + if isinstance(embedding, (list, tuple)): if len(embedding) != 1: raise TypeError("embedding must be a list of length 1") embedding = embedding[0] @@ -681,7 +681,7 @@ def create_key_and_extra_args(self, polynomial, name, check, embedding, latex_na raise TypeError("structure must be a list of length 1") structure = structure[0] - return (polynomial.base_ring(), polynomial, name, embedding, latex_name, maximize_at_primes, assume_disc_small, structure), {"check":check} + return (polynomial.base_ring(), polynomial, name, embedding, latex_name, maximize_at_primes, assume_disc_small, structure), {"check": check} def create_object(self, version, key, check): r""" @@ -10808,7 +10808,7 @@ def _repr_(self): sage: CyclotomicField(400)._repr_() 'Cyclotomic Field of order 400 and degree 160' """ - n = self._n + n = self.__n return f"Cyclotomic Field of order {n} and degree {self.degree()}" def _n(self): @@ -11952,7 +11952,7 @@ def __init__(self, polynomial, name=None, latex_name=None, check=True, embedding # we must set the flag _standard_embedding *before* any element creation # Note that in the following code, no element is built. if self.coerce_embedding() is not None and CDF.has_coerce_map_from(self): - rootD = CDF(number_field_element_quadratic.NumberFieldElement_quadratic(self, (QQ(0),QQ(1)))) + rootD = CDF(number_field_element_quadratic.NumberFieldElement_quadratic(self, (QQ(0), QQ(1)))) if D > 0: self._standard_embedding = rootD.real() > 0 else: @@ -12548,7 +12548,7 @@ def is_real_place(v): return False -def _splitting_classes_gens_(K,m,d): +def _splitting_classes_gens_(K, m, d): r""" Given a number field `K` of conductor `m` and degree `d`, this returns a set of multiplicative generators of the @@ -12597,7 +12597,7 @@ def map_Zmstar_to_Zm(h): Hgens = [] H = Zmstar.subgroup([]) p = 0 - Horder = arith.euler_phi(m)/d + Horder = arith.euler_phi(m) / d for g in Zmstar: if H.order() == Horder: break From 5f2315fa0bb94cb19f0c53268f6676b29ee6e602 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:32:43 -0700 Subject: [PATCH 340/414] build/pkgs/tornado: Update to 6.2 --- build/pkgs/tornado/checksums.ini | 6 +++--- build/pkgs/tornado/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/tornado/checksums.ini b/build/pkgs/tornado/checksums.ini index e0e818876c0..9f69d85d5dc 100644 --- a/build/pkgs/tornado/checksums.ini +++ b/build/pkgs/tornado/checksums.ini @@ -1,5 +1,5 @@ tarball=tornado-VERSION.tar.gz -sha1=5fc7bd2ccb94efb8fdc7c9438eca454f2f5bab9b -md5=f324f5e7607798552359d6ab054c4321 -cksum=3079653030 +sha1=2fa6cbd83ebafad83f49e89fbd5bbd20c42bbdc9 +md5=32fbad606b439c3e1bf4e79d4e872741 +cksum=3183867326 upstream_url=https://pypi.io/packages/source/t/tornado/tornado-VERSION.tar.gz diff --git a/build/pkgs/tornado/package-version.txt b/build/pkgs/tornado/package-version.txt index a435f5a56fa..0cda48ac61e 100644 --- a/build/pkgs/tornado/package-version.txt +++ b/build/pkgs/tornado/package-version.txt @@ -1 +1 @@ -6.1 +6.2 From 31014d934889b91e1d7ec64efdf579ab2e347227 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:35:47 -0700 Subject: [PATCH 341/414] build/pkgs/terminado: Update to 0.17.0 --- build/pkgs/terminado/checksums.ini | 6 +++--- build/pkgs/terminado/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/terminado/checksums.ini b/build/pkgs/terminado/checksums.ini index 794b2cda764..27333946973 100644 --- a/build/pkgs/terminado/checksums.ini +++ b/build/pkgs/terminado/checksums.ini @@ -1,5 +1,5 @@ tarball=terminado-VERSION.tar.gz -sha1=b0ff75a4f024dc07c9a819c1a63d75908624a5d3 -md5=e3fe92b48b3885ffa19b9890ed41578f -cksum=1261401246 +sha1=e2e37fe16c03d5bb6ab035c78e99a89bc742384c +md5=cf5f5f7dd1ece772f16013ad355b75e1 +cksum=2626848318 upstream_url=https://pypi.io/packages/source/t/terminado/terminado-VERSION.tar.gz diff --git a/build/pkgs/terminado/package-version.txt b/build/pkgs/terminado/package-version.txt index a5510516948..c5523bd09b1 100644 --- a/build/pkgs/terminado/package-version.txt +++ b/build/pkgs/terminado/package-version.txt @@ -1 +1 @@ -0.15.0 +0.17.0 From 8c1f91d8ac5ea3294abded103f6ece927c3526ba Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:39:01 -0700 Subject: [PATCH 342/414] build/pkgs/jupyter_client: Update to 7.4.4 --- build/pkgs/jupyter_client/checksums.ini | 6 +++--- build/pkgs/jupyter_client/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/jupyter_client/checksums.ini b/build/pkgs/jupyter_client/checksums.ini index 6f89053ffab..7e31fb35fc6 100644 --- a/build/pkgs/jupyter_client/checksums.ini +++ b/build/pkgs/jupyter_client/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyter_client-VERSION.tar.gz -sha1=99e1ce34f9022acbd3cc301d501ff83099f559ff -md5=3863b317e78ba701f5b844c2221a101f -cksum=1126052456 +sha1=0a9446eda476e3614d4509db0646ae5a89f6b492 +md5=481db492a8a0d16022c49481438e6285 +cksum=3316985535 upstream_url=https://pypi.io/packages/source/j/jupyter_client/jupyter_client-VERSION.tar.gz diff --git a/build/pkgs/jupyter_client/package-version.txt b/build/pkgs/jupyter_client/package-version.txt index c968a5762bf..4e61aeef901 100644 --- a/build/pkgs/jupyter_client/package-version.txt +++ b/build/pkgs/jupyter_client/package-version.txt @@ -1 +1 @@ -7.3.4 +7.4.4 From 1721d58ac35e654f2acd6df72c3cb557b98245d4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:42:31 -0700 Subject: [PATCH 343/414] build/pkgs/jupyter_core: Update to 4.11.2 --- build/pkgs/jupyter_core/checksums.ini | 6 +++--- build/pkgs/jupyter_core/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/jupyter_core/checksums.ini b/build/pkgs/jupyter_core/checksums.ini index 88fbd83a188..5a49b040b98 100644 --- a/build/pkgs/jupyter_core/checksums.ini +++ b/build/pkgs/jupyter_core/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyter_core-VERSION.tar.gz -sha1=f5db3f86f2cf40c9371bfa16a783a79a3502cac4 -md5=812d7410ffb4b671b23a68ef4bf40c12 -cksum=2575874942 +sha1=6e48f90477c11ad41b9404732a2bdcb485f4e630 +md5=84d207d4c48513a2b87ff2ed508beb98 +cksum=2072829465 upstream_url=https://pypi.io/packages/source/j/jupyter_core/jupyter_core-VERSION.tar.gz diff --git a/build/pkgs/jupyter_core/package-version.txt b/build/pkgs/jupyter_core/package-version.txt index 2da4316236a..4f89fb96069 100644 --- a/build/pkgs/jupyter_core/package-version.txt +++ b/build/pkgs/jupyter_core/package-version.txt @@ -1 +1 @@ -4.10.0 +4.11.2 From 9d723b509ff96e20a815feed535b98dc10575308 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:45:39 -0700 Subject: [PATCH 344/414] build/pkgs/nbconvert: Update to 6.5.3 --- build/pkgs/nbconvert/checksums.ini | 6 +++--- build/pkgs/nbconvert/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/nbconvert/checksums.ini b/build/pkgs/nbconvert/checksums.ini index e10e8b224e8..b3512a1d552 100644 --- a/build/pkgs/nbconvert/checksums.ini +++ b/build/pkgs/nbconvert/checksums.ini @@ -1,5 +1,5 @@ tarball=nbconvert-VERSION.tar.gz -sha1=061c9f18039aa8b6c5f83fc2281cbf8f6c4f3198 -md5=486a48c4dc3986e8801058273964d189 -cksum=183380977 +sha1=2bbadb417b371c8ae46163007e5c4c1c27204f0e +md5=6bb8f731af28bbdc7502f6d524d7d347 +cksum=2406762264 upstream_url=https://pypi.io/packages/source/n/nbconvert/nbconvert-VERSION.tar.gz diff --git a/build/pkgs/nbconvert/package-version.txt b/build/pkgs/nbconvert/package-version.txt index f22d756da39..db0785f2737 100644 --- a/build/pkgs/nbconvert/package-version.txt +++ b/build/pkgs/nbconvert/package-version.txt @@ -1 +1 @@ -6.5.0 +6.5.3 From 5b0d5f9c46036f052d9cb76524781ab9f7b04c3f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:50:48 -0700 Subject: [PATCH 345/414] build/pkgs/nbconvert: Update to 7.2.3, change to wheel package --- build/pkgs/nbconvert/SPKG.rst | 11 +++++++++++ build/pkgs/nbconvert/checksums.ini | 10 +++++----- build/pkgs/nbconvert/package-version.txt | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/build/pkgs/nbconvert/SPKG.rst b/build/pkgs/nbconvert/SPKG.rst index 98ee0393814..be08606f92a 100644 --- a/build/pkgs/nbconvert/SPKG.rst +++ b/build/pkgs/nbconvert/SPKG.rst @@ -6,3 +6,14 @@ Description jupyter nbconvert converts notebooks to various other formats via Jinja templates. + +License +------- + +BSD + +Upstream Contact +---------------- + +https://pypi.org/project/nbconvert/ + diff --git a/build/pkgs/nbconvert/checksums.ini b/build/pkgs/nbconvert/checksums.ini index b3512a1d552..1c3926c6ce3 100644 --- a/build/pkgs/nbconvert/checksums.ini +++ b/build/pkgs/nbconvert/checksums.ini @@ -1,5 +1,5 @@ -tarball=nbconvert-VERSION.tar.gz -sha1=2bbadb417b371c8ae46163007e5c4c1c27204f0e -md5=6bb8f731af28bbdc7502f6d524d7d347 -cksum=2406762264 -upstream_url=https://pypi.io/packages/source/n/nbconvert/nbconvert-VERSION.tar.gz +tarball=nbconvert-VERSION-py3-none-any.whl +sha1=de3dd5e475d84c2d143c03dfc22bfc490c03092f +md5=942dd716bd6976c58fdbcfec97bfbe20 +cksum=610202196 +upstream_url=https://pypi.io/packages/py3/n/nbconvert/nbconvert-VERSION-py3-none-any.whl diff --git a/build/pkgs/nbconvert/package-version.txt b/build/pkgs/nbconvert/package-version.txt index db0785f2737..429dc57af3a 100644 --- a/build/pkgs/nbconvert/package-version.txt +++ b/build/pkgs/nbconvert/package-version.txt @@ -1 +1 @@ -6.5.3 +7.2.3 From d91e7c085bd64c0933b517de96106b65fa5c12a7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 1 Nov 2022 00:53:17 -0700 Subject: [PATCH 346/414] build/pkgs/mistune: Update to 2.0.4 --- build/pkgs/mistune/checksums.ini | 6 +++--- build/pkgs/mistune/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/mistune/checksums.ini b/build/pkgs/mistune/checksums.ini index fe33dfb5b7d..b8b60db1d7b 100644 --- a/build/pkgs/mistune/checksums.ini +++ b/build/pkgs/mistune/checksums.ini @@ -1,5 +1,5 @@ tarball=mistune-VERSION.tar.gz -sha1=fd210c038fa7b0f2dffad6847b17dc139e7dd9fe -md5=fb6ab174ece938dea09f8b2adad771e4 -cksum=4120783541 +sha1=c15d02c98d04a3e615c3c1932d1b9a3b1759067a +md5=a4437edb22cf6519a7c61730fecb1a3f +cksum=2925260381 upstream_url=https://pypi.io/packages/source/m/mistune/mistune-VERSION.tar.gz diff --git a/build/pkgs/mistune/package-version.txt b/build/pkgs/mistune/package-version.txt index b60d71966ae..2165f8f9b6a 100644 --- a/build/pkgs/mistune/package-version.txt +++ b/build/pkgs/mistune/package-version.txt @@ -1 +1 @@ -0.8.4 +2.0.4 From b694ec2e30b611701f18b553e74c1dc6fef420ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 2 Nov 2022 19:25:26 +0100 Subject: [PATCH 347/414] remove old commented code, fix more pep8 --- src/sage/rings/number_field/number_field.py | 140 ++++---------------- 1 file changed, 26 insertions(+), 114 deletions(-) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index b670965e0c9..95e468f16cd 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -2077,7 +2077,7 @@ def structure(self): """ if self._structure is None: f = self.hom(self) - return f,f + return f, f else: return self._structure.create_structure(self) @@ -2635,7 +2635,7 @@ def quadratic_defect(self, a, p, check=True): return Infinity return v # The dyadic case - s = self(F.lift((1/F(a)).sqrt())) + s = self(F.lift((1 / F(a)).sqrt())) a = self(s**2) * a u = self(4).valuation(p) w = (a - 1).valuation(p) @@ -3551,7 +3551,7 @@ def ideal(self, *gens, **kwds): except ValueError: return sage.rings.ring.Ring.ideal(self, gens, **kwds) - def idealchinese(self,ideals,residues): + def idealchinese(self, ideals, residues): r""" Return a solution of the Chinese Remainder Theorem problem for ideals in a number field. @@ -3599,16 +3599,15 @@ def idealchinese(self,ideals,residues): ....: for P,k in I.factor() ....: ) True - """ factorizations = [I.factor() for I in ideals] - y = [a for a,f in zip(residues,factorizations) for _ in f] + y = [a for a, f in zip(residues, factorizations) for _ in f] x = pari.Mat([ - pari.Col([p.pari_prime(),k]) + pari.Col([p.pari_prime(), k]) for f in factorizations - for p,k in f + for p, k in f ]).mattranspose() - r = self.pari_nf().idealchinese(x,y) + r = self.pari_nf().idealchinese(x, y) return self(r) def fractional_ideal(self, *gens, **kwds): @@ -6921,10 +6920,10 @@ def trace_pairing(self, v): import sage.matrix.matrix_space A = sage.matrix.matrix_space.MatrixSpace(self.base_ring(), len(v))(0) for i in range(len(v)): - for j in range(i,len(v)): + for j in range(i, len(v)): t = (self(v[i]*v[j])).trace() - A[i,j] = t - A[j,i] = t + A[i, j] = t + A[j, i] = t return A def uniformizer(self, P, others="positive"): @@ -7166,7 +7165,7 @@ def unit_group(self, proof=None): except AttributeError: pass - U = UnitGroup(self,proof) + U = UnitGroup(self, proof) if proof: self._unit_group = U else: @@ -7292,7 +7291,7 @@ def S_unit_group(self, proof=None, S=None): except KeyError: pass - U = UnitGroup(self,proof,S=S) + U = UnitGroup(self, proof, S=S) if proof: self._S_unit_group_cache[S] = U else: @@ -7650,8 +7649,8 @@ def solve_CRT(self, reslist, Ilist, check=True): raise ArithmeticError("ideals in solve_CRT() must be pairwise coprime") x = ((1-r)*reslist[0]+r*reslist[1]).mod(prod(Ilist)) else: # n>2;, use induction / recursion - x = self.solve_CRT([reslist[0],self.solve_CRT(reslist[1:],Ilist[1:])], - [Ilist[0],prod(Ilist[1:])], check=check) + x = self.solve_CRT([reslist[0], self.solve_CRT(reslist[1:], Ilist[1:])], + [Ilist[0], prod(Ilist[1:])], check=check) if check and not all(x - xi in Ii for xi, Ii in zip(reslist, Ilist)): raise RuntimeError("Error in number field solve_CRT()") return self(x) @@ -8188,7 +8187,7 @@ def _coerce_from_other_number_field(self, x): # they are equal. The factor 128 is arbitrary, it is an extra # safety margin. eps = (log_root_diff - 7.0).exp2() - are_roots_equal = lambda a,b: (a-b).abs() <= eps + are_roots_equal = lambda a, b: (a-b).abs() <= eps if F is CC: # Adjust the precision of F, sufficient to represent all # the temporaries in the computation with a precision @@ -8558,8 +8557,8 @@ def optimized_subfields(self, degree=0, name=None, both_maps=True): Defn: a |--> -1/2*a6^3 + a6^2 - 1/2*a6) ] """ - return self._subfields_helper(degree=degree,name=name, - both_maps=both_maps,optimize=True) + return self._subfields_helper(degree=degree, name=name, + both_maps=both_maps, optimize=True) def change_names(self, names): r""" @@ -9294,8 +9293,8 @@ def minkowski_embedding(self, B=None, prec=None): for i in range(s): z = places[r+i](B[col]) - d[(r+2*i,col)] = z.real()*sqrt2 - d[(r+2*i+1,col)] = z.imag()*sqrt2 + d[(r+2*i, col)] = z.real()*sqrt2 + d[(r+2*i+1, col)] = z.imag()*sqrt2 return sage.matrix.all.matrix(d) @@ -10315,7 +10314,7 @@ def phi(x): assert phi(a) == v, "oops" return a - def hilbert_conductor(self,a,b): + def hilbert_conductor(self, a, b): """ This is the product of all (finite) primes where the Hilbert symbol is -1. What is the same, this is the (reduced) discriminant of the quaternion @@ -10347,7 +10346,7 @@ def hilbert_conductor(self,a,b): a, b = self(a), self(b) d = self.ideal(1) for p in set(self.ideal(2).prime_factors()).union(self.ideal(a).prime_factors()).union(self.ideal(b).prime_factors()): - if self.hilbert_symbol(a,b,p) == -1: + if self.hilbert_symbol(a, b, p) == -1: d *= p return d @@ -11143,103 +11142,16 @@ def _element_constructor_(self, x, check=True): elif (sage.interfaces.gap.is_GapElement(x) or isinstance(x, sage.libs.gap.element.GapElement)): return self._coerce_from_gap(x) - elif isinstance(x,str): + elif isinstance(x, str): return self._convert_from_str(x) # late import because of speed from sage.rings.universal_cyclotomic_field import UniversalCyclotomicFieldElement - if isinstance(x,UniversalCyclotomicFieldElement): + if isinstance(x, UniversalCyclotomicFieldElement): return x.to_cyclotomic_field(self) else: return self._convert_non_number_field_element(x) - # TODO: - # The following is very nice and much more flexible / powerful. - # However, it is simply not *consistent*, since it totally - # breaks the doctests in eisenstein_submodule.py. - # FIX THIS. - -# def _will_be_better_coerce_from_other_cyclotomic_field(self, x, only_canonical=False): -# """ -# Coerce an element x of a cyclotomic field into self, if at all possible. - -# INPUT: -# x -- number field element - -# only_canonical -- bool (default: False); Attempt to work, -# even in some cases when x is not in a subfield of -# the cyclotomics (as long as x is a root of unity). - -# EXAMPLES:: - -# sage: k5 = CyclotomicField(5) -# sage: k3 = CyclotomicField(3) -# sage: k15 = CyclotomicField(15) -# sage: k15._coerce_from_other_cyclotomic_field(k3.gen()) -# zeta15^5 -# sage: k15._coerce_from_other_cyclotomic_field(k3.gen()^2 + 17/3) -# -zeta15^5 + 14/3 -# sage: k3._coerce_from_other_cyclotomic_field(k15.gen()^5) -# zeta3 -# sage: k3._coerce_from_other_cyclotomic_field(-2/3 * k15.gen()^5 + 2/3) -# -2/3*zeta3 + 2/3 -# """ - -# K = x.parent() - -# if K is self: -# return x -# n = K.zeta_order() -# m = self.zeta_order() - -# self_gen = self.gen() - -# if m % n == 0: # easy case -# # pass this off to a method in the element class -# # it can be done very quickly and easily by the cython<->NTL -# # interface there -# return x._lift_cyclotomic_element(self) - -# # Whatever happens below, it has to be consistent with -# # zeta_r |---> (zeta_s)^m - -# if m % 2 and not n%2: -# m *= 2 -# self_gen = -self_gen - -# if only_canonical and m % n: -# raise TypeError, "no canonical coercion" - -# if not is_CyclotomicField(K): -# raise TypeError, "x must be in a cyclotomic field" - -# v = x.list() - -# # Find the smallest power r >= 1 of the generator g of K that is in self, -# # i.e., find the smallest r such that g^r has order dividing m. - -# d = sage.arith.all.gcd(m,n) -# r = n // d - -# # Since we use the power basis for cyclotomic fields, if every -# # v[i] with i not divisible by r is 0, then we're good. - -# # If h generates self and has order m, then the element g^r -# # maps to the power of self of order gcd(m,n)., i.e., h^(m/gcd(m,n)) -# # -# z = self_gen**(m // d) -# w = self(1) - -# a = self(0) -# for i in range(len(v)): -# if i%r: -# if v[i]: -# raise TypeError, "element does not belong to cyclotomic field" -# else: -# a += w*v[i] -# w *= z -# return a - def _coerce_from_other_cyclotomic_field(self, x, only_canonical=False): """ Coerce an element x of a cyclotomic field into self, if at all @@ -11345,7 +11257,7 @@ def _coerce_from_gap(self, x): return self(QQ(x)) coeffs = x.CoeffsCyc(self.__n) zeta = self.gen() - return sum(QQ(c)*zeta**i for i,c in enumerate(coeffs)) + return sum(QQ(c) * zeta**i for i, c in enumerate(coeffs)) def _Hom_(self, codomain, cat=None): """ @@ -11496,7 +11408,7 @@ def embeddings(self, K): # zeta not defined return super().embeddings(K) else: - X = [m for m in range(n) if arith.gcd(m,n) == 1] + X = (m for m in range(n) if arith.gcd(m, n) == 1) v = [self.hom([z**i], check=False) for i in X] else: v = [] @@ -11744,7 +11656,7 @@ def _multiplicative_order_table(self): zeta = self.zeta(n) # todo: this desperately needs to be optimized!!! for i in range(n): - t[x.polynomial()] = n//arith.GCD(m,n) # multiplicative_order of (zeta_n)**m + t[x.polynomial()] = n // arith.GCD(m, n) # multiplicative_order of (zeta_n)**m x *= zeta m += 1 self.__multiplicative_order_table = t From 93dc05ccd9a6bf3358e57902cf84da87b0d91339 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Thu, 3 Nov 2022 14:21:10 +0800 Subject: [PATCH 348/414] prevent assertion failure in _discrete_log_pgroup when group is trivial --- .../additive_abelian_wrapper.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py index 7c0aec05ff8..1894ac24bc1 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py +++ b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py @@ -371,6 +371,9 @@ def discrete_log(self, x, gens=None): y = cofactor * x pvals = [o.valuation(p) for o in ords] + if not any(pvals): + continue + plog = _discrete_log_pgroup(p, pvals, pgens, y) for i, (r, v) in enumerate(zip(plog, pvals)): @@ -472,7 +475,7 @@ def _element_constructor_(self, x, check=False): def _discrete_log_pgroup(p, vals, aa, b): r""" Attempt to express an element of p-power order in terms of - generators of a p-subgroup of this group. + generators of a nontrivial p-subgroup of this group. Used as a subroutine in :meth:`discrete_log`. @@ -491,6 +494,18 @@ def _discrete_log_pgroup(p, vals, aa, b): sage: from sage.groups.additive_abelian.additive_abelian_wrapper import _discrete_log_pgroup sage: _discrete_log_pgroup(5, [1,2,4,4], gs, a + 17*b + 123*c + 456*d) (1, 17, 123, 456) + + TESTS: + + Check for :trac:`34716`:: + + sage: E = EllipticCurve(GF(487^2), [311,205]) + sage: G = E.abelian_group().torsion_subgroup(42) + sage: G.invariants() + (6, 42) + sage: P, Q = G.torsion_subgroup(6).gens() + sage: G.discrete_log(2*P + 3*Q, [P, Q]) # indirect doctest + (2, 3) """ from itertools import product as iproduct From a155a98e4ba797bc0286dd7e29d852e8c7653055 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 4 Jul 2022 12:17:25 +0800 Subject: [PATCH 349/414] move carmichael_lambda to sage.arith.misc --- src/sage/arith/misc.py | 154 ++++++++++++++++++++++++++++++++++++++++ src/sage/crypto/util.py | 152 --------------------------------------- 2 files changed, 154 insertions(+), 152 deletions(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 1bd4d4c6a1f..071d99c3bf2 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -3134,6 +3134,160 @@ def plot(self, xmin=1, xmax=50, pointsize=30, rgbcolor=(0, 0, 1), euler_phi = Euler_Phi() +def carmichael_lambda(n): + r""" + Return the Carmichael function of a positive integer ``n``. + + The Carmichael function of `n`, denoted `\lambda(n)`, is the smallest + positive integer `k` such that `a^k \equiv 1 \pmod{n}` for all + `a \in \ZZ/n\ZZ` satisfying `\gcd(a, n) = 1`. Thus, `\lambda(n) = k` + is the exponent of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. + + INPUT: + + - ``n`` -- a positive integer. + + OUTPUT: + + - The Carmichael function of ``n``. + + ALGORITHM: + + If `n = 2, 4` then `\lambda(n) = \varphi(n)`. Let `p \geq 3` be an odd + prime and let `k` be a positive integer. Then + `\lambda(p^k) = p^{k - 1}(p - 1) = \varphi(p^k)`. If `k \geq 3`, then + `\lambda(2^k) = 2^{k - 2}`. Now consider the case where `n > 3` is + composite and let `n = p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t}` be the + prime factorization of `n`. Then + + .. MATH:: + + \lambda(n) + = \lambda(p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t}) + = \text{lcm}(\lambda(p_1^{k_1}), \lambda(p_2^{k_2}), \dots, \lambda(p_t^{k_t})) + + EXAMPLES: + + The Carmichael function of all positive integers up to and including 10:: + + sage: from sage.arith.misc import carmichael_lambda + sage: list(map(carmichael_lambda, [1..10])) + [1, 1, 2, 2, 4, 2, 6, 2, 6, 4] + + The Carmichael function of the first ten primes:: + + sage: list(map(carmichael_lambda, primes_first_n(10))) + [1, 2, 4, 6, 10, 12, 16, 18, 22, 28] + + Cases where the Carmichael function is equivalent to the Euler phi + function:: + + sage: carmichael_lambda(2) == euler_phi(2) + True + sage: carmichael_lambda(4) == euler_phi(4) + True + sage: p = random_prime(1000, lbound=3, proof=True) + sage: k = randint(1, 1000) + sage: carmichael_lambda(p^k) == euler_phi(p^k) + True + + A case where `\lambda(n) \neq \varphi(n)`:: + + sage: k = randint(3, 1000) + sage: carmichael_lambda(2^k) == 2^(k - 2) + True + sage: carmichael_lambda(2^k) == 2^(k - 2) == euler_phi(2^k) + False + + Verifying the current implementation of the Carmichael function using + another implementation. The other implementation that we use for + verification is an exhaustive search for the exponent of the + multiplicative group `(\ZZ/n\ZZ)^{\ast}`. :: + + sage: from sage.arith.misc import carmichael_lambda + sage: n = randint(1, 500) + sage: c = carmichael_lambda(n) + sage: def coprime(n): + ....: return [i for i in range(n) if gcd(i, n) == 1] + sage: def znpower(n, k): + ....: L = coprime(n) + ....: return list(map(power_mod, L, [k]*len(L), [n]*len(L))) + sage: def my_carmichael(n): + ....: if n == 1: + ....: return 1 + ....: for k in range(1, n): + ....: L = znpower(n, k) + ....: ones = [1] * len(L) + ....: T = [L[i] == ones[i] for i in range(len(L))] + ....: if all(T): + ....: return k + sage: c == my_carmichael(n) + True + + Carmichael's theorem states that `a^{\lambda(n)} \equiv 1 \pmod{n}` + for all elements `a` of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. + Here, we verify Carmichael's theorem. :: + + sage: from sage.arith.misc import carmichael_lambda + sage: n = randint(2, 1000) + sage: c = carmichael_lambda(n) + sage: ZnZ = IntegerModRing(n) + sage: M = ZnZ.list_of_elements_of_multiplicative_group() + sage: ones = [1] * len(M) + sage: P = [power_mod(a, c, n) for a in M] + sage: P == ones + True + + TESTS: + + The input ``n`` must be a positive integer:: + + sage: from sage.arith.misc import carmichael_lambda + sage: carmichael_lambda(0) + Traceback (most recent call last): + ... + ValueError: Input n must be a positive integer. + sage: carmichael_lambda(randint(-10, 0)) + Traceback (most recent call last): + ... + ValueError: Input n must be a positive integer. + + Bug reported in :trac:`8283`:: + + sage: from sage.arith.misc import carmichael_lambda + sage: type(carmichael_lambda(16)) + + + REFERENCES: + + - :wikipedia:`Carmichael_function` + """ + n = Integer(n) + # sanity check + if n < 1: + raise ValueError("Input n must be a positive integer.") + + L = n.factor() + t = [] + + # first get rid of the prime factor 2 + if n & 1 == 0: + e = L[0][1] + L = L[1:] # now, n = 2**e * L.value() + if e < 3: # for 1 <= k < 3, lambda(2**k) = 2**(k - 1) + e = e - 1 + else: # for k >= 3, lambda(2**k) = 2**(k - 2) + e = e - 2 + t.append(1 << e) # 2**e + + # then other prime factors + t += [p**(k - 1) * (p - 1) for p, k in L] + + # finish the job + from .functions import lcm + return lcm(t) + + def crt(a, b, m=None, n=None): r""" Return a solution to a Chinese Remainder Theorem problem. diff --git a/src/sage/crypto/util.py b/src/sage/crypto/util.py index 42b262c9858..cc149941777 100644 --- a/src/sage/crypto/util.py +++ b/src/sage/crypto/util.py @@ -255,158 +255,6 @@ def bin_to_ascii(B): A.append(chr(ascii_integer(b[8*i: 8*(i+1)]))) return "".join(A) -def carmichael_lambda(n): - r""" - Return the Carmichael function of a positive integer ``n``. - - The Carmichael function of `n`, denoted `\lambda(n)`, is the smallest - positive integer `k` such that `a^k \equiv 1 \pmod{n}` for all - `a \in \ZZ/n\ZZ` satisfying `\gcd(a, n) = 1`. Thus, `\lambda(n) = k` - is the exponent of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. - - INPUT: - - - ``n`` -- a positive integer. - - OUTPUT: - - - The Carmichael function of ``n``. - - ALGORITHM: - - If `n = 2, 4` then `\lambda(n) = \varphi(n)`. Let `p \geq 3` be an odd - prime and let `k` be a positive integer. Then - `\lambda(p^k) = p^{k - 1}(p - 1) = \varphi(p^k)`. If `k \geq 3`, then - `\lambda(2^k) = 2^{k - 2}`. Now consider the case where `n > 3` is - composite and let `n = p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t}` be the - prime factorization of `n`. Then - - .. MATH:: - - \lambda(n) - = \lambda(p_1^{k_1} p_2^{k_2} \cdots p_t^{k_t}) - = \text{lcm}(\lambda(p_1^{k_1}), \lambda(p_2^{k_2}), \dots, \lambda(p_t^{k_t})) - - EXAMPLES: - - The Carmichael function of all positive integers up to and including 10:: - - sage: from sage.crypto.util import carmichael_lambda - sage: list(map(carmichael_lambda, [1..10])) - [1, 1, 2, 2, 4, 2, 6, 2, 6, 4] - - The Carmichael function of the first ten primes:: - - sage: list(map(carmichael_lambda, primes_first_n(10))) - [1, 2, 4, 6, 10, 12, 16, 18, 22, 28] - - Cases where the Carmichael function is equivalent to the Euler phi - function:: - - sage: carmichael_lambda(2) == euler_phi(2) - True - sage: carmichael_lambda(4) == euler_phi(4) - True - sage: p = random_prime(1000, lbound=3, proof=True) - sage: k = randint(1, 1000) - sage: carmichael_lambda(p^k) == euler_phi(p^k) - True - - A case where `\lambda(n) \neq \varphi(n)`:: - - sage: k = randint(3, 1000) - sage: carmichael_lambda(2^k) == 2^(k - 2) - True - sage: carmichael_lambda(2^k) == 2^(k - 2) == euler_phi(2^k) - False - - Verifying the current implementation of the Carmichael function using - another implementation. The other implementation that we use for - verification is an exhaustive search for the exponent of the - multiplicative group `(\ZZ/n\ZZ)^{\ast}`. :: - - sage: from sage.crypto.util import carmichael_lambda - sage: n = randint(1, 500) - sage: c = carmichael_lambda(n) - sage: def coprime(n): - ....: return [i for i in range(n) if gcd(i, n) == 1] - sage: def znpower(n, k): - ....: L = coprime(n) - ....: return list(map(power_mod, L, [k]*len(L), [n]*len(L))) - sage: def my_carmichael(n): - ....: if n == 1: - ....: return 1 - ....: for k in range(1, n): - ....: L = znpower(n, k) - ....: ones = [1] * len(L) - ....: T = [L[i] == ones[i] for i in range(len(L))] - ....: if all(T): - ....: return k - sage: c == my_carmichael(n) - True - - Carmichael's theorem states that `a^{\lambda(n)} \equiv 1 \pmod{n}` - for all elements `a` of the multiplicative group `(\ZZ/n\ZZ)^{\ast}`. - Here, we verify Carmichael's theorem. :: - - sage: from sage.crypto.util import carmichael_lambda - sage: n = randint(2, 1000) - sage: c = carmichael_lambda(n) - sage: ZnZ = IntegerModRing(n) - sage: M = ZnZ.list_of_elements_of_multiplicative_group() - sage: ones = [1] * len(M) - sage: P = [power_mod(a, c, n) for a in M] - sage: P == ones - True - - TESTS: - - The input ``n`` must be a positive integer:: - - sage: from sage.crypto.util import carmichael_lambda - sage: carmichael_lambda(0) - Traceback (most recent call last): - ... - ValueError: Input n must be a positive integer. - sage: carmichael_lambda(randint(-10, 0)) - Traceback (most recent call last): - ... - ValueError: Input n must be a positive integer. - - Bug reported in :trac:`8283`:: - - sage: from sage.crypto.util import carmichael_lambda - sage: type(carmichael_lambda(16)) - - - REFERENCES: - - - :wikipedia:`Carmichael_function` - """ - n = Integer(n) - # sanity check - if n < 1: - raise ValueError("Input n must be a positive integer.") - - L = n.factor() - t = [] - - # first get rid of the prime factor 2 - if n & 1 == 0: - e = L[0][1] - L = L[1:] # now, n = 2**e * L.value() - if e < 3: # for 1 <= k < 3, lambda(2**k) = 2**(k - 1) - e = e - 1 - else: # for k >= 3, lambda(2**k) = 2**(k - 2) - e = e - 2 - t.append(1 << e) # 2**e - - # then other prime factors - t += [p**(k - 1) * (p - 1) for p, k in L] - - # finish the job - return lcm(t) - def has_blum_prime(lbound, ubound): r""" From 846d280319df122e9649153a0d38e70294181585 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 4 Jul 2022 12:18:22 +0800 Subject: [PATCH 350/414] make carmichael_lambda() available by default --- src/sage/arith/all.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/arith/all.py b/src/sage/arith/all.py index d89f401f211..c93e9f05bc9 100644 --- a/src/sage/arith/all.py +++ b/src/sage/arith/all.py @@ -9,8 +9,8 @@ inverse_mod, get_gcd, get_inverse_mod, power_mod, rational_reconstruction, mqrr_rational_reconstruction, trial_division, factor, prime_divisors, odd_part, prime_to_m_part, - is_square, is_squarefree, euler_phi, crt, CRT, CRT_list, CRT_basis, - CRT_vectors, multinomial, multinomial_coefficients, + is_square, is_squarefree, euler_phi, carmichael_lambda, crt, CRT, + CRT_list, CRT_basis, CRT_vectors, multinomial, multinomial_coefficients, binomial, factorial, kronecker_symbol, kronecker, legendre_symbol, primitive_root, nth_prime, quadratic_residues, moebius, continuant, number_of_divisors, hilbert_symbol, hilbert_conductor, From ebf7e415eb73d2eb496d3debd7668e425333293c Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 4 Jul 2022 12:39:11 +0800 Subject: [PATCH 351/414] simplify power-of-2 handling in carmichael_lambda() --- src/sage/arith/misc.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 071d99c3bf2..9caf22c1f06 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -33,6 +33,7 @@ from sage.rings.abc import RealField, ComplexField from sage.rings.fast_arith import arith_int, arith_llong, prime_range +from sage.arith.functions import LCM_list ################################################################## @@ -3272,20 +3273,16 @@ def carmichael_lambda(n): # first get rid of the prime factor 2 if n & 1 == 0: - e = L[0][1] - L = L[1:] # now, n = 2**e * L.value() - if e < 3: # for 1 <= k < 3, lambda(2**k) = 2**(k - 1) - e = e - 1 - else: # for k >= 3, lambda(2**k) = 2**(k - 2) - e = e - 2 - t.append(1 << e) # 2**e + two,e = L.pop(0) + assert two == 2 + k = e - 2 if e >= 3 else e - 1 + t.append(1 << k) # then other prime factors t += [p**(k - 1) * (p - 1) for p, k in L] # finish the job - from .functions import lcm - return lcm(t) + return LCM_list(t) def crt(a, b, m=None, n=None): From 37ec15490ada135df0d2e4377f7092603435f3d4 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 4 Jul 2022 12:41:05 +0800 Subject: [PATCH 352/414] deprecation for sage.crypto.carmichael_lambda --- src/sage/crypto/util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sage/crypto/util.py b/src/sage/crypto/util.py index cc149941777..aa013f9aca4 100644 --- a/src/sage/crypto/util.py +++ b/src/sage/crypto/util.py @@ -25,6 +25,9 @@ from sage.rings.integer import Integer from sage.rings.finite_rings.integer_mod import Mod as mod +from sage.misc.lazy_import import lazy_import +lazy_import('sage.arith.misc', ('carmichael_lambda'), deprecation=34719) + def ascii_integer(B): r""" Return the ASCII integer corresponding to the binary string ``B``. From 7e9aefca872ab761410a5283c2a1c0701aadff4a Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Fri, 4 Nov 2022 16:06:39 +0800 Subject: [PATCH 353/414] fix doctest failures --- src/sage/arith/misc.py | 2 +- src/sage/crypto/stream.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 9caf22c1f06..e03c4b95b4d 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -3268,7 +3268,7 @@ def carmichael_lambda(n): if n < 1: raise ValueError("Input n must be a positive integer.") - L = n.factor() + L = list(n.factor()) t = [] # first get rid of the prime factor 2 diff --git a/src/sage/crypto/stream.py b/src/sage/crypto/stream.py index f0554d7a6cc..728e73ca59b 100644 --- a/src/sage/crypto/stream.py +++ b/src/sage/crypto/stream.py @@ -288,7 +288,7 @@ def blum_blum_shub(length, seed=None, p=None, q=None, is the period. :: sage: from sage.crypto.stream import blum_blum_shub - sage: from sage.crypto.util import carmichael_lambda + sage: from sage.arith.misc import carmichael_lambda sage: carmichael_lambda(carmichael_lambda(7*11)) 4 sage: s = [GF(2)(int(str(x))) for x in blum_blum_shub(60, p=7, q=11, seed=13)] From 7eea8892b6d584d74f80c0554ae31a67e58cdff8 Mon Sep 17 00:00:00 2001 From: Johann Birnick Date: Fri, 4 Nov 2022 19:55:05 +0000 Subject: [PATCH 354/414] changed function name --- .../polynomial/multi_polynomial_ring_base.pyx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index 6e66b079a1e..befcf7c4172 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -347,7 +347,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): """ return self.remove_var(x)[str(x)] - def multivariate_interpolation(self, bound, *args): + def interpolation(self, bound, *args): """ Create a polynomial with specified evaluations. @@ -355,9 +355,9 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): This function can be called in two ways: - 1. multivariate_interpolation(bound, points, values) + 1. interpolation(bound, points, values) - 2. multivariate_interpolation(bound, function) + 2. interpolation(bound, function) INPUT: @@ -386,7 +386,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): ....: return a^3*b + b + c^2 + 25 ....: sage: R. = PolynomialRing(QQ) - sage: R.multivariate_interpolation(4, F) + sage: R.interpolation(4, F) x^3*y + z^2 + y + 25 @@ -394,7 +394,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): ....: return a^3*b + b + c^2 + 25 ....: sage: R. = PolynomialRing(QQ) - sage: R.multivariate_interpolation([3,1,2], F) + sage: R.interpolation([3,1,2], F) x^3*y + z^2 + y + 25 @@ -406,7 +406,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): ....: (2,7,0),(1,10,13),(0,0,1),(-1,1,0),(2,5,3),(1,1,1),(7,4,11), ....: (12,1,9),(1,1,3),(4,-1,2),(0,1,5),(5,1,3),(3,1,-2),(2,11,3), ....: (4,12,19),(3,1,1),(5,2,-3),(12,1,1),(2,3,4)] - sage: R.multivariate_interpolation([3,1,2], points, [F(*x) for x in points]) + sage: R.interpolation([3,1,2], points, [F(*x) for x in points]) x^3*y + z^2 + y + 25 ALGORITHM: @@ -424,6 +424,9 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): Also, if the solution is not unique, it spits out one solution, without any notice that there are more. + Lastly, the interpolation function for univariate polynomial rings + is called ``lagrange_polynomial()``. + .. WARNING:: If you don't provide point/value pairs but just a function, it @@ -437,7 +440,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): ....: return a^3*b + b + c^2 + 25 ....: sage: R. = PolynomialRing(QQ) - sage: R.multivariate_interpolation(3,F) + sage: R.interpolation(3,F) 1/2*x^3 + x*y + z^2 - 1/2*x + y + 25 .. SEEALSO:: From a6e99a73a0bb120cfe91b2bf487e3b6580205ab2 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 7 Nov 2022 14:23:10 +0800 Subject: [PATCH 355/414] use abs(m) to compute m-division polynomial --- src/sage/schemes/elliptic_curves/ell_generic.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 154a581bc2a..d66d868fc80 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -2350,10 +2350,23 @@ def multiplication_by_m_isogeny(self, m): sage: E.multiplication_by_m_isogeny(2).rational_maps() ((1/4*x^4 + 33/4*x^2 - 121/2*x + 363/4)/(x^3 - 3/4*x^2 - 33/2*x + 121/4), (-1/256*x^7 + 1/128*x^6*y - 7/256*x^6 - 3/256*x^5*y - 105/256*x^5 - 165/256*x^4*y + 1255/256*x^4 + 605/128*x^3*y - 473/64*x^3 - 1815/128*x^2*y - 10527/256*x^2 + 2541/128*x*y + 4477/32*x - 1331/128*y - 30613/256)/(1/16*x^6 - 3/32*x^5 - 519/256*x^4 + 341/64*x^3 + 1815/128*x^2 - 3993/64*x + 14641/256)) + + Test for :trac:`34727`:: + + sage: E = EllipticCurve([5,5]) + sage: E.multiplication_by_m_isogeny(-1) + Isogeny of degree 1 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Rational Field to Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Rational Field + sage: E.multiplication_by_m_isogeny(-2) + Isogeny of degree 4 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Rational Field to Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Rational Field + sage: E.multiplication_by_m_isogeny(-3) + Isogeny of degree 9 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Rational Field to Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Rational Field + sage: mu = E.multiplication_by_m_isogeny + sage: all(mu(-m) == -mu(m) for m in (1,2,3,5,7)) + True """ mx, my = self.multiplication_by_m(m) - torsion_poly = self.torsion_polynomial(m).monic() + torsion_poly = self.torsion_polynomial(abs(m)).monic() phi = self.isogeny(torsion_poly, codomain=self) phi._EllipticCurveIsogeny__initialize_rational_maps(precomputed_maps=(mx, my)) From 94a9718bb25511f52ee7c1a8890c7a9a257822ee Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Tue, 8 Nov 2022 14:37:10 +0900 Subject: [PATCH 356/414] Rebase sage autodoc --- src/doc/en/prep/Programming.rst | 2 +- .../en/prep/Symbolics-and-Basic-Plotting.rst | 2 +- src/sage/arith/misc.py | 8 +- src/sage/combinat/root_system/cartan_type.py | 2 +- src/sage/combinat/sf/classical.py | 2 +- src/sage/finance/option.pyx | 2 +- src/sage/graphs/strongly_regular_db.pyx | 2 +- src/sage/interfaces/sage0.py | 2 +- src/sage/libs/singular/ring.pyx | 2 +- src/sage/manifolds/manifold.py | 2 +- src/sage/misc/c3_controlled.pyx | 2 +- src/sage/misc/sagedoc_conf.py | 4 +- src/sage/misc/sageinspect.py | 2 +- .../rings/finite_rings/hom_finite_field.pyx | 4 +- .../rings/function_field/function_field.py | 2 +- .../rings/polynomial/laurent_polynomial.pyx | 6 +- src/sage/sets/disjoint_set.pyx | 8 +- src/sage/structure/category_object.pyx | 6 +- src/sage/structure/factory.pyx | 2 +- src/sage/structure/global_options.py | 2 +- src/sage/structure/list_clone.pyx | 4 +- src/sage/symbolic/expression.pyx | 1 - src/sage/symbolic/function.pyx | 17 +- src/sage/symbolic/getitem_impl.pxi | 2 +- src/sage/symbolic/ring.pyx | 7 +- src/sage_docbuild/conf.py | 6 +- src/sage_docbuild/ext/sage_autodoc.py | 2997 ++++++++++++----- src/sage_setup/cython_options.py | 5 + 28 files changed, 2211 insertions(+), 892 deletions(-) diff --git a/src/doc/en/prep/Programming.rst b/src/doc/en/prep/Programming.rst index 3ae980667df..056326927b6 100644 --- a/src/doc/en/prep/Programming.rst +++ b/src/doc/en/prep/Programming.rst @@ -109,7 +109,7 @@ It is very important to keep in the parentheses. :: sage: A.det # Won't work - + This is so useful because we can use the 'tab' key, remember! diff --git a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst index 6e0ab69765a..bf11ef481b1 100644 --- a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst +++ b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst @@ -178,7 +178,7 @@ This is a good place for a few reminders of basic help. :: sage: z.simplify - + Finally, recall that you can get nicely typeset versions of the output in several ways. diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 1bd4d4c6a1f..0eca5554dc9 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -2163,9 +2163,9 @@ def get_gcd(order): EXAMPLES:: sage: sage.arith.misc.get_gcd(4000) - + sage: sage.arith.misc.get_gcd(400000) - + sage: sage.arith.misc.get_gcd(4000000000) """ @@ -2185,9 +2185,9 @@ def get_inverse_mod(order): EXAMPLES:: sage: sage.arith.misc.get_inverse_mod(6000) - + sage: sage.arith.misc.get_inverse_mod(600000) - + sage: sage.arith.misc.get_inverse_mod(6000000000) """ diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index dafe2f62366..d9857727ab1 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -2025,7 +2025,7 @@ def classical(self): We check that :meth:`classical`, :meth:`sage.combinat.root_system.cartan_type.CartanType_crystallographic.dynkin_diagram`, - and :meth:`.special_node` are consistent:: + and :meth:`special_node` are consistent:: sage: for ct in CartanType.samples(affine = True): ....: g1 = ct.classical().dynkin_diagram() diff --git a/src/sage/combinat/sf/classical.py b/src/sage/combinat/sf/classical.py index 0477629f3d1..3de5db72457 100644 --- a/src/sage/combinat/sf/classical.py +++ b/src/sage/combinat/sf/classical.py @@ -46,7 +46,7 @@ def init(): sage: sage.combinat.sf.classical.conversion_functions = {} sage: init() sage: sage.combinat.sf.classical.conversion_functions[('Schur', 'powersum')] - + The following checks if the bug described in :trac:`15312` is fixed. :: diff --git a/src/sage/finance/option.pyx b/src/sage/finance/option.pyx index 31b290e0ce9..79c514c4677 100644 --- a/src/sage/finance/option.pyx +++ b/src/sage/finance/option.pyx @@ -36,7 +36,7 @@ def black_scholes(double spot_price, double strike_price, double time_to_maturit sage: finance.black_scholes doctest:warning... DeprecationWarning: the package sage.finance is deprecated... - + sage: finance.black_scholes(42, 40, 0.5, 0.1, 0.2, 'call') # abs tol 1e-10 4.759422392871532 diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index 1e01738662d..58ae72ae4e2 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -1154,7 +1154,7 @@ def is_RSHCD(int v, int k, int l, int mu): sage: from sage.graphs.strongly_regular_db import is_RSHCD sage: t = is_RSHCD(64,27,10,12); t - [, 64, 27, 10, 12] + [, 64, 27, 10, 12] sage: g = t[0](*t[1:]); g Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) diff --git a/src/sage/interfaces/sage0.py b/src/sage/interfaces/sage0.py index a43a059e0ec..665a00f6525 100644 --- a/src/sage/interfaces/sage0.py +++ b/src/sage/interfaces/sage0.py @@ -554,7 +554,7 @@ def _repr_(self): EXAMPLES:: sage: sage0(4).gcd - + """ return str(self._obj.parent().eval('%s.%s' % (self._obj._name, self._name))) diff --git a/src/sage/libs/singular/ring.pyx b/src/sage/libs/singular/ring.pyx index 04bd16e8784..828e782d887 100644 --- a/src/sage/libs/singular/ring.pyx +++ b/src/sage/libs/singular/ring.pyx @@ -772,7 +772,7 @@ cpdef poison_currRing(frame, event, arg): sage: from sage.libs.singular.ring import poison_currRing sage: sys.settrace(poison_currRing) sage: sys.gettrace() - + sage: sys.settrace(previous_trace_func) # switch it off again """ global currRing diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index 09b040fb3fc..bb3f6cc42f5 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -2206,7 +2206,7 @@ class options(GlobalOptions): sage: M.options._reset() """ NAME = 'manifolds' - module = 'sage.manifolds' + module = 'sage.manifolds.manifold' option_class = 'TopologicalManifold' textbook_output = dict(default=True, description='textbook-like output instead of the Pynac output for derivatives', diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index ebadb070d1d..03561005657 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -1091,7 +1091,7 @@ class HierarchyElement(object, metaclass=ClasscallMetaclass): sage: x._bases [5, 2] sage: x._key - + sage: x._key(10) 10 diff --git a/src/sage/misc/sagedoc_conf.py b/src/sage/misc/sagedoc_conf.py index 83df405a3a1..2d2a950c23e 100644 --- a/src/sage/misc/sagedoc_conf.py +++ b/src/sage/misc/sagedoc_conf.py @@ -23,6 +23,7 @@ def process_docstring_aliases(app, what, name, obj, options, docstringlines): """ Change the docstrings for aliases to point to the original object. """ + basename = name.rpartition('.')[2] if hasattr(obj, '__name__') and obj.__name__ != basename: docstringlines[:] = ['See :obj:`%s`.' % name] @@ -146,9 +147,6 @@ def apply(self): node.rawsource = source node[:] = [nodes.Text(source)] -from sage.misc.sageinspect import sage_getargspec -autodoc_builtin_argspec = sage_getargspec - # This is only used by sage.misc.sphinxify def setup(app): app.connect('autodoc-process-docstring', process_docstring_cython) diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index fbca2defc20..3eb25382884 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -152,7 +152,7 @@ def is_function_or_cython_function(obj): sage: is_function_or_cython_function(_mul_parent) True sage: is_function_or_cython_function(Integer.digits) # unbound method - False + True sage: is_function_or_cython_function(Integer(1).digits) # bound method False diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index 81c7620d42d..75c91514aa2 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -435,7 +435,7 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): sage: Frob = k.frobenius_endomorphism() sage: embed = Frob.fixed_field()[1] sage: embed.__reduce__() # indirect doctest - (, + (, (, Set of field embeddings from Finite Field of size 5 to Finite Field in t of size 5^3, {}, @@ -835,7 +835,7 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): sage: k. = GF(5^3) sage: Frob = k.frobenius_endomorphism(2) sage: Frob.__reduce__() # indirect doctest - (, + (, (, Automorphism group of Finite Field in t of size 5^3, {}, diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index e8cf51beda6..f1e130e3b0e 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -128,7 +128,7 @@ sage: TestSuite(S).run() # long time (4s) Global function fields ----------------------- +====================== A global function field in Sage is an extension field of a rational function field over a *finite* constant field by an irreducible separable polynomial over the diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index 5e4188ed80f..f75d26a27b3 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -3308,12 +3308,12 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): to variables supplied in args. Multiple variables and iteration counts may be supplied; see - documentation for the global derivative() function for more + documentation for the global :func:`derivative` function for more details. .. SEEALSO:: - :meth:`_derivative` + :meth:`_derivative` EXAMPLES:: @@ -3336,7 +3336,7 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): respect to the given variable. If var is among the generators of this ring, the derivative - is with respect to the generator. Otherwise, _derivative(var) is called + is with respect to the generator. Otherwise, ``_derivative(var)`` is called recursively for each coefficient of this polynomial. .. SEEALSO:: :meth:`derivative` diff --git a/src/sage/sets/disjoint_set.pyx b/src/sage/sets/disjoint_set.pyx index aaa12438f15..dac6e75779b 100644 --- a/src/sage/sets/disjoint_set.pyx +++ b/src/sage/sets/disjoint_set.pyx @@ -358,14 +358,14 @@ cdef class DisjointSet_of_integers(DisjointSet_class): sage: d = DisjointSet(5) sage: d.__reduce__() - (, (5,), [0, 1, 2, 3, 4]) + (, (5,), [0, 1, 2, 3, 4]) :: sage: d.union(2,4) sage: d.union(1,3) sage: d.__reduce__() - (, (5,), [0, 1, 2, 1, 2]) + (, (5,), [0, 1, 2, 1, 2]) """ return DisjointSet, (self._nodes.degree,), self.__getstate__() @@ -674,7 +674,7 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): {{0}, {1}, {2}, {3}, {4}} sage: d = _ sage: d.__reduce__() - (, + (, ([0, 1, 2, 3, 4],), [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]) @@ -683,7 +683,7 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): sage: d.union(2,4) sage: d.union(1,3) sage: d.__reduce__() - (, + (, ([0, 1, 2, 3, 4],), [(0, 0), (1, 1), (2, 2), (3, 1), (4, 2)]) """ diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index 52abd7d918b..99679d2e3ca 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -571,7 +571,7 @@ cdef class CategoryObject(SageObject): sage: F.base_ring() Integer Ring sage: F.__class__.base_ring - + Note that the coordinates of the elements of a module can lie in a bigger ring, the ``coordinate_ring``:: @@ -591,7 +591,7 @@ cdef class CategoryObject(SageObject): sage: F.base_ring() Rational Field sage: F.__class__.base_ring - + sage: E = CombinatorialFreeModule(ZZ, [1,2,3]) sage: F = CombinatorialFreeModule(ZZ, [2,3,4]) @@ -599,7 +599,7 @@ cdef class CategoryObject(SageObject): sage: H.base_ring() Integer Ring sage: H.__class__.base_ring - + .. TODO:: diff --git a/src/sage/structure/factory.pyx b/src/sage/structure/factory.pyx index a4a13186973..4e656be479f 100644 --- a/src/sage/structure/factory.pyx +++ b/src/sage/structure/factory.pyx @@ -550,7 +550,7 @@ cdef class UniqueFactory(SageObject): sage: a = test_factory(1, 2) Making object (1, 2) sage: test_factory.reduce_data(a) - (, + (, (, (...), (1, 2), diff --git a/src/sage/structure/global_options.py b/src/sage/structure/global_options.py index 8a3411bdc8f..f4d983a0658 100644 --- a/src/sage/structure/global_options.py +++ b/src/sage/structure/global_options.py @@ -1390,7 +1390,7 @@ def __eq__(self, other): sage: Partitions.options == Tableaux.options False """ - return self.__getstate__() == other.__getstate__() + return isinstance(other, GlobalOptions) and self.__getstate__() == other.__getstate__() def _add_option(self, option, specifications): r""" diff --git a/src/sage/structure/list_clone.pyx b/src/sage/structure/list_clone.pyx index 125f32762ef..50e57b34ca9 100644 --- a/src/sage/structure/list_clone.pyx +++ b/src/sage/structure/list_clone.pyx @@ -934,7 +934,7 @@ cdef class ClonableArray(ClonableElement): sage: loads(dumps(el)) [1, 2, 4] sage: t = el.__reduce__(); t - (, + (, (, , [1, 2, 4], @@ -1720,7 +1720,7 @@ cdef class ClonableIntArray(ClonableElement): sage: loads(dumps(el)) [1, 2, 4] sage: t = el.__reduce__(); t - (, + (, (, , [1, 2, 4], diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 903a8541460..2c9c1ba894c 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -6363,7 +6363,6 @@ cdef class Expression(Expression_abc): sage: type(u._unpack_operands()[0]) <... 'tuple'> """ - from sage.symbolic.expression import unpack_operands return unpack_operands(self) def operands(self): diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index 0baedce69c0..24cb7adc6c1 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -141,11 +141,6 @@ is attempted, and after that ``sin()`` which succeeds:: from sage.structure.sage_object cimport SageObject from sage.structure.element cimport Element, parent, Expression from sage.misc.lazy_attribute import lazy_attribute -from .expression import ( - call_registered_function, find_registered_function, register_or_update_function, - get_sfunction_from_hash -) -from .expression import get_sfunction_from_serial as get_sfunction_from_serial from sage.structure.coerce cimport (coercion_model, py_scalar_to_element, is_numpy_type, is_mpmath_type) @@ -263,6 +258,8 @@ cdef class Function(SageObject): f(x) """ + from .expression import register_or_update_function + self._serial = register_or_update_function(self, self._name, self._latex_name, self._nargs, self._evalf_params_first, False) @@ -544,6 +541,8 @@ cdef class Function(SageObject): if not isinstance(a, Expression): raise TypeError("arguments must be symbolic expressions") + from .expression import call_registered_function + return call_registered_function(self._serial, self._nargs, args, hold, not symbolic_input, SR) @@ -838,6 +837,8 @@ cdef class GinacFunction(BuiltinFunction): preserved_arg=preserved_arg, alt_name=alt_name) cdef _is_registered(self): + from .expression import find_registered_function, get_sfunction_from_serial + # Since this is function is defined in C++, it is already in # ginac's function registry fname = self._ginac_name if self._ginac_name is not None else self._name @@ -845,6 +846,8 @@ cdef class GinacFunction(BuiltinFunction): return bool(get_sfunction_from_serial(self._serial)) cdef _register_function(self): + from .expression import register_or_update_function + # We don't need to add anything to GiNaC's function registry # However, if any custom methods were provided in the python class, # we should set the properties of the function_options object @@ -1094,6 +1097,8 @@ cdef class BuiltinFunction(Function): sage: loads(dumps(cot)) == cot # trac #15138 True """ + from .expression import find_registered_function, get_sfunction_from_serial + # check if already defined cdef unsigned int serial @@ -1191,6 +1196,8 @@ cdef class SymbolicFunction(Function): evalf_params_first) cdef _is_registered(SymbolicFunction self): + from .expression import get_sfunction_from_hash + # see if there is already a SymbolicFunction with the same state cdef long myhash = self._hash_() cdef SymbolicFunction sfunc = get_sfunction_from_hash(myhash) diff --git a/src/sage/symbolic/getitem_impl.pxi b/src/sage/symbolic/getitem_impl.pxi index 72bf9d5f0e5..ff8a9d94138 100644 --- a/src/sage/symbolic/getitem_impl.pxi +++ b/src/sage/symbolic/getitem_impl.pxi @@ -182,7 +182,7 @@ cdef class OperandsWrapper(SageObject): TESTS:: sage: (x^2).op.__reduce__() - (, (x^2,)) + (, (x^2,)) sage: loads(dumps((x^2).op)) Operands of x^2 """ diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 2036a7331d4..77002bef25c 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -52,8 +52,11 @@ from sage.structure.coerce cimport is_numpy_type import sage.rings.abc from sage.rings.integer_ring import ZZ -# is_SymbolicVariable used to be defined here; re-export it -from sage.symbolic.expression import _is_SymbolicVariable as is_SymbolicVariable +# is_SymbolicVariable used to be defined here; re-export it here lazily +cpdef bint is_SymbolicVariable(x): + from sage.symbolic.expression import _is_SymbolicVariable + + return _is_SymbolicVariable(x) import keyword import operator diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 4c880cdca63..a369978cf1d 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -40,12 +40,12 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - 'sage_docbuild.ext.inventory_builder', - 'sage_docbuild.ext.multidocs', - 'sage_docbuild.ext.sage_autodoc', 'sphinx.ext.todo', 'sphinx.ext.extlinks', 'sphinx.ext.mathjax', + 'sage_docbuild.ext.sage_autodoc', + 'sage_docbuild.ext.inventory_builder', + 'sage_docbuild.ext.multidocs', 'IPython.sphinxext.ipython_directive', 'matplotlib.sphinxext.plot_directive', 'jupyter_sphinx', diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 0d15eec91a2..418cf67dbd6 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -1,20 +1,21 @@ -# -*- coding: utf-8 -*- """ Sage autodoc extension -This is based on :mod:`sphinx.ext.autodoc` from Sphinx. +This is :mod:`sphinx.ext.autodoc` extension modified for Sage objects. -From :mod:`sphinx.ext.autodoc`: +The original headline of :mod:`sphinx.ext.autodoc`: + + Extension to create automatic documentation from code docstrings. Automatically insert docstrings for functions, classes or whole modules into the doctree, thus avoiding duplication between docstrings and documentation for those who like elaborate docstrings. - :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. +Presently this module is based on :mod:`sphinx.ext.autodoc` from Sphinx version 5.3.0. +The upstream original source file is `sphinx/ext/autodoc/__init__.py `_. -The upstream original can be found at -https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/autodoc/__init__.py +In the source file of this module, major modifications are delimited by a pair +of comment dividers. To lessen maintenance burdens, we aim at reducing those modifications. AUTHORS: @@ -25,38 +26,49 @@ - Simon King (2011-04): use sageinspect; include public cython attributes only in the documentation if they have a docstring -- Kwankyu Lee (2018-12-26): rebased on the latest sphinx.ext.autodoc +- Kwankyu Lee (2018-12-26, 2022-11-08): rebased on the latest sphinx.ext.autodoc """ -import inspect import re -import sys +import warnings +from inspect import Parameter, Signature +from types import ModuleType +from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Sequence, + Set, Tuple, Type, TypeVar, Union) -from docutils.statemachine import ViewList +from docutils.statemachine import StringList import sphinx -from sphinx.ext.autodoc import mock, ObjectMember -from sphinx.ext.autodoc.importer import import_object, get_object_members +from sphinx.application import Sphinx +from sphinx.config import ENUM, Config +from sphinx.deprecation import RemovedInSphinx60Warning +from sphinx.environment import BuildEnvironment +from sphinx.ext.autodoc.importer import (get_class_members, get_object_members, import_module, + import_object) +from sphinx.ext.autodoc.mock import ismock, mock, undecorate from sphinx.locale import _, __ -from sphinx.pycode import ModuleAnalyzer -from sphinx.errors import PycodeError -from sphinx.util import logging -from sphinx.util.docstrings import prepare_docstring -from sphinx.util.inspect import isdescriptor, \ - safe_getattr, object_description, is_builtin_class_method, \ - isenumattribute, isclassmethod, isstaticmethod, getdoc - +from sphinx.pycode import ModuleAnalyzer, PycodeError +from sphinx.util import inspect, logging +from sphinx.util.docstrings import prepare_docstring, separate_metadata +from sphinx.util.inspect import (evaluate_signature, getdoc, object_description, safe_getattr, + stringify_signature) +from sphinx.util.typing import OptionSpec, get_type_hints, restify +from sphinx.util.typing import stringify as stringify_typehint + +# ------------------------------------------------------------------ from sage.misc.sageinspect import (sage_getdoc_original, sage_getargspec, isclassinstance, sage_formatargspec, is_function_or_cython_function) -from sage.misc.lazy_import import LazyImport -# This is used to filter objects of classes that inherit from -# ClasscallMetaclass. See the class AttributeDocumenter below. -from sage.misc.classcall_metaclass import ClasscallMetaclass +_getdoc = getdoc +def getdoc(obj, *args, **kwargs): + return sage_getdoc_original(obj) +# ------------------------------------------------------------------ +if TYPE_CHECKING: + from sphinx.ext.autodoc.directive import DocumenterBridge logger = logging.getLogger(__name__) @@ -66,7 +78,7 @@ MethodDescriptorType = type(type.__subclasses__) -# extended signature RE: with explicit module name separated by ``::`` +#: extended signature RE: with explicit module name separated by ``::``. py_ext_sig_re = re.compile( r'''^ ([\w.]+::)? # explicit module name ([\w.]+\.)? # module and/or class name(s) @@ -75,116 +87,119 @@ (?:\s* -> \s* (.*))? # return annotation )? $ # and nothing more ''', re.VERBOSE) +special_member_re = re.compile(r'^__\S+__$') -def identity(x): - # type: (Any) -> Any +def identity(x: Any) -> Any: return x -ALL = object() +class _All: + """A special value for :*-members: that matches to any member.""" + + def __contains__(self, item: Any) -> bool: + return True + + def append(self, item: Any) -> None: + pass # nothing + + +class _Empty: + """A special value for :exclude-members: that never matches to any member.""" + + def __contains__(self, item: Any) -> bool: + return False + + +ALL = _All() +EMPTY = _Empty() +UNINITIALIZED_ATTR = object() INSTANCEATTR = object() +SLOTSATTR = object() -def members_option(arg): - # type: (Any) -> Union[object, List[unicode]] +def members_option(arg: Any) -> Union[object, List[str]]: """Used to convert the :members: option to auto directives.""" - if arg is None: + if arg in (None, True): return ALL - return [x.strip() for x in arg.split(',')] + elif arg is False: + return None + else: + return [x.strip() for x in arg.split(',') if x.strip()] -def members_set_option(arg): - # type: (Any) -> Union[object, Set[unicode]] - """Used to convert the :members: option to auto directives.""" - if arg is None: - return ALL - return set(x.strip() for x in arg.split(',')) +def exclude_members_option(arg: Any) -> Union[object, Set[str]]: + """Used to convert the :exclude-members: option.""" + if arg in (None, True): + return EMPTY + return {x.strip() for x in arg.split(',') if x.strip()} + + +def inherited_members_option(arg: Any) -> Set[str]: + """Used to convert the :inherited-members: option to auto directives.""" + if arg in (None, True): + return {'object'} + elif arg: + return {x.strip() for x in arg.split(',')} + else: + return set() + + +def member_order_option(arg: Any) -> Optional[str]: + """Used to convert the :member-order: option to auto directives.""" + if arg in (None, True): + return None + elif arg in ('alphabetical', 'bysource', 'groupwise'): + return arg + else: + raise ValueError(__('invalid value for member-order option: %s') % arg) + + +def class_doc_from_option(arg: Any) -> Optional[str]: + """Used to convert the :class-doc-from: option to autoclass directives.""" + if arg in ('both', 'class', 'init'): + return arg + else: + raise ValueError(__('invalid value for class-doc-from option: %s') % arg) SUPPRESS = object() -def annotation_option(arg): - # type: (Any) -> Any - if arg is None: +def annotation_option(arg: Any) -> Any: + if arg in (None, True): # suppress showing the representation of the object return SUPPRESS else: return arg -def bool_option(arg): - # type: (Any) -> bool +def bool_option(arg: Any) -> bool: """Used to convert flag options to auto directives. (Instead of directives.flag(), which returns None). """ return True -def formatargspec(function, args, varargs=None, varkw=None, defaults=None, - kwonlyargs=(), kwonlydefaults={}, annotations={}): - """ - Sphinx's version of formatargspec is deprecated, so use Sage's instead. - """ - return sage_formatargspec(args, varargs=varargs, varkw=varkw, defaults=defaults, - kwonlyargs=kwonlyargs, kwonlydefaults=kwonlydefaults, - annotations=annotations) - - -class AutodocReporter(): - """ - A reporter replacement that assigns the correct source name - and line number to a system message, as recorded in a ViewList. +def merge_members_option(options: Dict) -> None: + """Merge :private-members: and :special-members: options to the + :members: option. """ - def __init__(self, viewlist, reporter): - # type: (ViewList, Reporter) -> None - self.viewlist = viewlist - self.reporter = reporter - - def __getattr__(self, name): - # type: (unicode) -> Any - return getattr(self.reporter, name) - - def system_message(self, level, message, *children, **kwargs): - # type: (int, unicode, Any, Any) -> nodes.system_message - if 'line' in kwargs and 'source' not in kwargs: - try: - source, line = self.viewlist.items[kwargs['line']] - except IndexError: - pass - else: - kwargs['source'] = source - kwargs['line'] = line - return self.reporter.system_message(level, message, - *children, **kwargs) - - def debug(self, *args, **kwargs): - # type: (Any, Any) -> nodes.system_message - if self.reporter.debug_flag: - return self.system_message(0, *args, **kwargs) + if options.get('members') is ALL: + # merging is not needed when members: ALL + return - def info(self, *args, **kwargs): - # type: (Any, Any) -> nodes.system_message - return self.system_message(1, *args, **kwargs) - - def warning(self, *args, **kwargs): - # type: (Any, Any) -> nodes.system_message - return self.system_message(2, *args, **kwargs) - - def error(self, *args, **kwargs): - # type: (Any, Any) -> nodes.system_message - return self.system_message(3, *args, **kwargs) - - def severe(self, *args, **kwargs): - # type: (Any, Any) -> nodes.system_message - return self.system_message(4, *args, **kwargs) + members = options.setdefault('members', []) + for key in {'private-members', 'special-members'}: + if key in options and options[key] not in (ALL, None): + for member in options[key]: + if member not in members: + members.append(member) # Some useful event listener factories for autodoc-process-docstring. -def cut_lines(pre, post=0, what=None): - # type: (int, int, unicode) -> Callable +def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable: """Return a listener that removes the first *pre* and last *post* lines of every docstring. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. @@ -194,11 +209,10 @@ def cut_lines(pre, post=0, what=None): from sphinx.ext.autodoc import cut_lines app.connect('autodoc-process-docstring', cut_lines(4, what=['module'])) - This can (and should) be used in place of ``automodule_skip_lines`` (config - value defined in Sphinx autodoc). + This can (and should) be used in place of ``automodule_skip_lines``. """ - def process(app, what_, name, obj, options, lines): - # type: (Sphinx, unicode, unicode, Any, Any, List[unicode]) -> None + def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] + ) -> None: if what and what_ not in what: return del lines[:pre] @@ -213,8 +227,8 @@ def process(app, what_, name, obj, options, lines): return process -def between(marker, what=None, keepempty=False, exclude=False): - # type: (unicode, Sequence[unicode], bool, bool) -> Callable +def between(marker: str, what: Sequence[str] = None, keepempty: bool = False, + exclude: bool = False) -> Callable: """Return a listener that either keeps, or if *exclude* is True excludes, lines between lines that match the *marker* regular expression. If no line matches, the resulting docstring would be empty, so no change will be made @@ -225,8 +239,8 @@ def between(marker, what=None, keepempty=False, exclude=False): """ marker_re = re.compile(marker) - def process(app, what_, name, obj, options, lines): - # type: (Sphinx, unicode, unicode, Any, Any, List[unicode]) -> None + def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] + ) -> None: if what and what_ not in what: return deleted = 0 @@ -249,7 +263,50 @@ def process(app, what_, name, obj, options, lines): return process -class Documenter(): +# This class is used only in ``sphinx.ext.autodoc.directive``, +# But we define this class here to keep compatibility (see #4538) +class Options(dict): + """A dict/attribute hybrid that returns None on nonexisting keys.""" + def copy(self) -> "Options": + return Options(super().copy()) + + def __getattr__(self, name: str) -> Any: + try: + return self[name.replace('_', '-')] + except KeyError: + return None + + +class ObjectMember(tuple): + """A member of object. + + This is used for the result of ``Documenter.get_object_members()`` to + represent each member of the object. + + .. Note:: + + An instance of this class behaves as a tuple of (name, object) + for compatibility to old Sphinx. The behavior will be dropped + in the future. Therefore extensions should not use the tuple + interface. + """ + + def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any: + return super().__new__(cls, (name, obj)) # type: ignore + + def __init__(self, name: str, obj: Any, docstring: Optional[str] = None, + class_: Any = None, skipped: bool = False) -> None: + self.__name__ = name + self.object = obj + self.docstring = docstring + self.skipped = skipped + self.class_ = class_ + + +ObjectMembers = Union[List[ObjectMember], List[Tuple[str, Any]]] + + +class Documenter: """ A Documenter knows how to autodocument a single object type. When registered with the AutoDirective, it will be used to document objects @@ -262,13 +319,13 @@ class Documenter(): A Documenter has an *option_spec* that works like a docutils directive's; in fact, it will be used to parse an auto directive's options that matches - the documenter. + the Documenter. """ #: name by which the directive is called (auto...) and the default #: generated directive name objtype = 'object' #: indentation by which to indent the directive content - content_indent = u' ' + content_indent = ' ' #: priority if multiple documenters return True from can_document_member priority = 0 #: order if autodoc_member_order is set to 'groupwise' @@ -276,57 +333,59 @@ class Documenter(): #: true if the generated content may contain titles titles_allowed = False - option_spec = {'noindex': bool_option} # type: Dict[unicode, Callable] + option_spec: OptionSpec = { + 'noindex': bool_option + } - def get_attr(self, obj, name, *defargs): - # type: (Any, unicode, Any) -> Any + def get_attr(self, obj: Any, name: str, *defargs: Any) -> Any: """getattr() override for types such as Zope interfaces.""" return autodoc_attrgetter(self.env.app, obj, name, *defargs) @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool - """Called to see if a member can be documented by this documenter.""" + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + """Called to see if a member can be documented by this Documenter.""" raise NotImplementedError('must be implemented in subclasses') - def __init__(self, directive, name, indent=u''): - # type: (DocumenterBridge, unicode, unicode) -> None + def __init__(self, directive: "DocumenterBridge", name: str, indent: str = '') -> None: self.directive = directive - self.env = directive.env # type: BuildEnvironment + self.config: Config = directive.env.config + self.env: BuildEnvironment = directive.env self.options = directive.genopt self.name = name self.indent = indent # the module and object path within the module, and the fully # qualified name (all set after resolve_name succeeds) - self.modname = None # type: str - self.module = None # type: ModuleType - self.objpath = None # type: List[unicode] - self.fullname = None # type: unicode + self.modname: str = None + self.module: ModuleType = None + self.objpath: List[str] = None + self.fullname: str = None # extra signature items (arguments and return annotation, # also set after resolve_name succeeds) - self.args = None # type: unicode - self.retann = None # type: unicode + self.args: str = None + self.retann: str = None # the object to document (set after import_object succeeds) - self.object = None # type: Any - self.object_name = None # type: unicode + self.object: Any = None + self.object_name: str = None # the parent/owner of the object to document - self.parent = None # type: Any + self.parent: Any = None # the module analyzer to get at attribute docs, or None - self.analyzer = None # type: Any + self.analyzer: ModuleAnalyzer = None @property - def documenters(self): - # type: () -> Dict[unicode, Type[Documenter]] + def documenters(self) -> Dict[str, Type["Documenter"]]: """Returns registered Documenter classes""" - return get_documenters(self.env.app) + return self.env.app.registry.documenters - def add_line(self, line, source, *lineno): - # type: (unicode, unicode, int) -> None + def add_line(self, line: str, source: str, *lineno: int) -> None: """Append one line of generated reST to the output.""" - self.directive.result.append(self.indent + line, source, *lineno) + if line.strip(): # not a blank line + self.directive.result.append(self.indent + line, source, *lineno) + else: + self.directive.result.append('', source, *lineno) - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[unicode]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: """Resolve the module and name of the object to document given by the arguments and the current module/class. @@ -336,8 +395,7 @@ def resolve_name(self, modname, parents, path, base): """ raise NotImplementedError('must be implemented in subclasses') - def parse_name(self): - # type: () -> bool + def parse_name(self) -> bool: """Determine what module to import and what attribute to document. Returns True and sets *self.modname*, *self.objpath*, *self.fullname*, @@ -347,52 +405,57 @@ def parse_name(self): # functions can contain a signature which is then used instead of # an autogenerated one try: - explicit_modname, path, base, args, retann = \ - py_ext_sig_re.match(self.name).groups() # type: ignore + matched = py_ext_sig_re.match(self.name) + explicit_modname, path, base, args, retann = matched.groups() except AttributeError: - logger.warning('invalid signature for auto%s (%r)' % (self.objtype, self.name)) + logger.warning(__('invalid signature for auto%s (%r)') % (self.objtype, self.name), + type='autodoc') return False # support explicit module and class name separation via :: if explicit_modname is not None: modname = explicit_modname[:-2] - parents = path and path.rstrip('.').split('.') or [] + parents = path.rstrip('.').split('.') if path else [] else: modname = None parents = [] - self.modname, self.objpath = self.resolve_name(modname, parents, path, base) + with mock(self.config.autodoc_mock_imports): + self.modname, self.objpath = self.resolve_name(modname, parents, path, base) if not self.modname: return False self.args = args self.retann = retann - self.fullname = (self.modname or '') + \ - (self.objpath and '.' + '.'.join(self.objpath) or '') + self.fullname = ((self.modname or '') + + ('.' + '.'.join(self.objpath) if self.objpath else '')) return True - def import_object(self): - # type: () -> bool + def import_object(self, raiseerror: bool = False) -> bool: """Import the object given by *self.modname* and *self.objpath* and set it as *self.object*. Returns True if successful, False if an error occurred. """ - with mock(self.env.config.autodoc_mock_imports): + with mock(self.config.autodoc_mock_imports): try: ret = import_object(self.modname, self.objpath, self.objtype, attrgetter=self.get_attr, - warningiserror=self.env.config.autodoc_warningiserror) + warningiserror=self.config.autodoc_warningiserror) self.module, self.parent, self.object_name, self.object = ret + if ismock(self.object): + self.object = undecorate(self.object) return True except ImportError as exc: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - return False + if raiseerror: + raise + else: + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False - def get_real_modname(self): - # type: () -> str + def get_real_modname(self) -> str: """Get the real module name of an object to document. It can differ from the name of the module through which the object was @@ -400,29 +463,27 @@ def get_real_modname(self): """ return self.get_attr(self.object, '__module__', None) or self.modname - def check_module(self): - # type: () -> bool + def check_module(self) -> bool: """Check if *self.object* is really defined in the module given by *self.modname*. """ if self.options.imported_members: return True - modname = self.get_attr(self.object, '__module__', None) + subject = inspect.unpartial(self.object) + modname = self.get_attr(subject, '__module__', None) if modname and modname != self.modname: return False return True - def format_args(self): - # type: () -> unicode + def format_args(self, **kwargs: Any) -> str: """Format the argument signature of *self.object*. Should return None if the object does not have a signature. """ return None - def format_name(self): - # type: () -> unicode + def format_name(self) -> str: """Format the name of *self.object*. This normally should be something that can be parsed by the generated @@ -433,68 +494,90 @@ def format_name(self): # directives of course) return '.'.join(self.objpath) or self.modname - def format_signature(self): - # type: () -> unicode + def _call_format_args(self, **kwargs: Any) -> str: + if kwargs: + try: + return self.format_args(**kwargs) + except TypeError: + # avoid chaining exceptions, by putting nothing here + pass + + # retry without arguments for old documenters + return self.format_args() + + def format_signature(self, **kwargs: Any) -> str: """Format the signature (arguments and return annotation) of the object. Let the user process it via the ``autodoc-process-signature`` event. """ + #if 'Expression.numerical_approx' in self.name: + # from celery.contrib import rdb; rdb.set_trace() if self.args is not None: # signature given explicitly - args = "(%s)" % self.args # type: unicode + args = "(%s)" % self.args + retann = self.retann else: # try to introspect the signature try: - args = self.format_args() - except Exception as err: - logger.warning('error while formatting arguments for %s: %s' % - (self.fullname, err)) + retann = None + args = self._call_format_args(**kwargs) + if args: + matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args) + if matched: + args = matched.group(1) + retann = matched.group(2) + except Exception as exc: + logger.warning(__('error while formatting arguments for %s: %s'), + self.fullname, exc, type='autodoc') args = None - retann = self.retann - - result = self.env.app.emit_firstresult( - 'autodoc-process-signature', self.objtype, self.fullname, - self.object, self.options, args, retann) + result = self.env.events.emit_firstresult('autodoc-process-signature', + self.objtype, self.fullname, + self.object, self.options, args, retann) if result: args, retann = result if args is not None: - return args + (retann and (' -> %s' % retann) or '') + return args + ((' -> %s' % retann) if retann else '') else: return '' - def add_directive_header(self, sig): - # type: (unicode) -> None + def add_directive_header(self, sig: str) -> None: """Add the directive header and options to the generated content.""" domain = getattr(self, 'domain', 'py') directive = getattr(self, 'directivetype', self.objtype) name = self.format_name() sourcename = self.get_sourcename() - self.add_line(u'.. %s:%s:: %s%s' % (domain, directive, name, sig), - sourcename) + + # one signature per line, indented by column + prefix = '.. %s:%s:: ' % (domain, directive) + for i, sig_line in enumerate(sig.split("\n")): + self.add_line('%s%s%s' % (prefix, name, sig_line), + sourcename) + if i == 0: + prefix = " " * len(prefix) + if self.options.noindex: - self.add_line(u' :noindex:', sourcename) + self.add_line(' :noindex:', sourcename) if self.objpath: # Be explicit about the module, this is necessary since .. class:: # etc. don't support a prepended module name - self.add_line(u' :module: %s' % self.modname, sourcename) - - def get_doc(self, encoding=None, ignore=1): - # type: (unicode, int) -> List[List[unicode]] - """Decode and return lines of the docstring(s) for the object.""" - docstring = sage_getdoc_original(self.object) - if docstring is None and self.env.config.autodoc_inherit_docstrings: - docstring = getdoc(self.object) - # make sure we have Unicode docstrings, then sanitize and split - # into lines - if isinstance(docstring, str): - return [prepare_docstring(docstring)] - # ... else it is something strange, let's ignore it + self.add_line(' :module: %s' % self.modname, sourcename) + + def get_doc(self) -> Optional[List[List[str]]]: + """Decode and return lines of the docstring(s) for the object. + + When it returns None, autodoc-process-docstring will not be called for this + object. + """ + docstring = getdoc(self.object, self.get_attr, self.config.autodoc_inherit_docstrings, + self.parent, self.object_name) + if docstring: + tab_width = self.directive.state.document.settings.tab_width + return [prepare_docstring(docstring, tab_width)] return [] - def process_doc(self, docstrings): - # type: (List[List[unicode]]) -> Iterator[unicode] + def process_doc(self, docstrings: List[List[str]]) -> Iterator[str]: """Let the user process the docstrings before adding them.""" for docstringlines in docstrings: if self.env.app: @@ -502,24 +585,31 @@ def process_doc(self, docstrings): self.env.app.emit('autodoc-process-docstring', self.objtype, self.fullname, self.object, self.options, docstringlines) - for line in docstringlines: - yield line - def get_sourcename(self): - # type: () -> unicode + if docstringlines and docstringlines[-1] != '': + # append a blank line to the end of the docstring + docstringlines.append('') + + yield from docstringlines + + def get_sourcename(self) -> str: + if (inspect.safe_getattr(self.object, '__module__', None) and + inspect.safe_getattr(self.object, '__qualname__', None)): + # Get the correct location of docstring from self.object + # to support inherited methods + fullname = '%s.%s' % (self.object.__module__, self.object.__qualname__) + else: + fullname = self.fullname + if self.analyzer: - # prevent encoding errors when the file name is non-ASCII - if not isinstance(self.analyzer.srcname, str): - filename = str(self.analyzer.srcname, - sys.getfilesystemencoding(), 'replace') - else: - filename = self.analyzer.srcname - return u'%s:docstring of %s' % (filename, self.fullname) - return u'docstring of %s' % self.fullname + return '%s:docstring of %s' % (self.analyzer.srcname, fullname) + else: + return 'docstring of %s' % fullname - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None + def add_content(self, more_content: Optional[StringList]) -> None: """Add content from docstrings, attribute documentation and user.""" + docstring = True + # set sourcename and add content from attribute documentation sourcename = self.get_sourcename() if self.analyzer: @@ -527,57 +617,64 @@ def add_content(self, more_content, no_docstring=False): if self.objpath: key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) if key in attr_docs: - no_docstring = True - docstrings = [attr_docs[key]] + docstring = False + # make a copy of docstring for attributes to avoid cache + # the change of autodoc-process-docstring event. + docstrings = [list(attr_docs[key])] + for i, line in enumerate(self.process_doc(docstrings)): self.add_line(line, sourcename, i) # add content from docstrings - if not no_docstring: - encoding = self.analyzer - docstrings = self.get_doc(encoding) - if not docstrings: - # append at least a dummy docstring, so that the event - # autodoc-process-docstring is fired and can add some - # content if desired - docstrings.append([]) - for i, line in enumerate(self.process_doc(docstrings)): - self.add_line(line, sourcename, i) + if docstring: + docstrings = self.get_doc() + if docstrings is None: + # Do not call autodoc-process-docstring on get_doc() returns None. + pass + else: + if not docstrings: + # append at least a dummy docstring, so that the event + # autodoc-process-docstring is fired and can add some + # content if desired + docstrings.append([]) + for i, line in enumerate(self.process_doc(docstrings)): + self.add_line(line, sourcename, i) # add additional content (e.g. from document), if present if more_content: for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) - def get_object_members(self, want_all): - # type: (bool) -> Tuple[bool, List[Tuple[unicode, Any]]] - """Return `(members_check_module, members)` where `members` is a - list of `(membername, member)` pairs of the members of *self.object*. + def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: + """Return ``(members_check_module, members)`` where ``members`` is a + list of ``(membername, member)`` pairs of the members of *self.object*. If *want_all* is True, return all members. Else, only return those - members given by *self.options.members* (which may also be none). + members given by *self.options.members* (which may also be None). """ + warnings.warn('The implementation of Documenter.get_object_members() will be ' + 'removed from Sphinx-6.0.', RemovedInSphinx60Warning) members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer) if not want_all: if not self.options.members: - return False, [] + return False, [] # type: ignore # specific members given selected = [] for name in self.options.members: if name in members: selected.append((name, members[name].value)) else: - logger.warning('missing attribute %s in object %s' % - (name, self.fullname)) - return False, sorted(selected) + logger.warning(__('missing attribute %s in object %s') % + (name, self.fullname), type='autodoc') + return False, selected elif self.options.inherited_members: - return False, sorted((m.name, m.value) for m in members.values()) + return False, [(m.name, m.value) for m in members.values()] else: - return False, sorted((m.name, m.value) for m in members.values() - if m.directly_defined) + return False, [(m.name, m.value) for m in members.values() + if m.directly_defined] - def filter_members(self, members, want_all): - # type: (List[Tuple[unicode, Any]], bool) -> List[Tuple[unicode, Any, bool]] + def filter_members(self, members: ObjectMembers, want_all: bool + ) -> List[Tuple[str, Any, bool]]: """Filter the given member list. Members are skipped if @@ -591,6 +688,23 @@ def filter_members(self, members, want_all): The user can override the skipping decision by connecting to the ``autodoc-skip-member`` event. """ + def is_filtered_inherited_member(name: str, obj: Any) -> bool: + inherited_members = self.options.inherited_members or set() + + if inspect.isclass(self.object): + for cls in self.object.__mro__: + if cls.__name__ in inherited_members and cls != self.object: + # given member is a member of specified *super class* + return True + elif name in cls.__dict__: + return False + elif name in self.get_attr(cls, '__annotations__', {}): + return False + elif isinstance(obj, ObjectMember) and obj.class_ is cls: + return False + + return False + ret = [] # search for members in source code too @@ -602,81 +716,129 @@ def filter_members(self, members, want_all): attr_docs = {} # process members and determine which to skip - for (membername, member) in members: - # Immediately skip lazy imports to avoid deprecation - # messages (#17455). - if isinstance(member, LazyImport): - continue + for obj in members: + try: + membername, member = obj + # --------------------------------------------------- + # Trac #17455: Immediately skip lazy imports to avoid + # deprecation messages. + from sage.misc.lazy_import import LazyImport + if isinstance(member, LazyImport): + continue + # --------------------------------------------------- + # if isattr is True, the member is documented as an attribute + if member is INSTANCEATTR: + isattr = True + elif (namespace, membername) in attr_docs: + isattr = True + else: + isattr = False - # if isattr is True, the member is documented as an attribute - isattr = False + doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings, + self.object, membername) + if not isinstance(doc, str): + # Ignore non-string __doc__ + doc = None - doc = self.get_attr(member, '__doc__', None) - if doc is None and self.env.config.autodoc_inherit_docstrings: - doc = getdoc(member) + # if the member __doc__ is the same as self's __doc__, it's just + # inherited and therefore not the member's doc + cls = self.get_attr(member, '__class__', None) + if cls: + cls_doc = self.get_attr(cls, '__doc__', None) + if cls_doc == doc: + doc = None + + if isinstance(obj, ObjectMember) and obj.docstring: + # hack for ClassDocumenter to inject docstring via ObjectMember + doc = obj.docstring + + doc, metadata = separate_metadata(doc) + has_doc = bool(doc) + + if 'private' in metadata: + # consider a member private if docstring has "private" metadata + isprivate = True + elif 'public' in metadata: + # consider a member public if docstring has "public" metadata + isprivate = False + else: + isprivate = membername.startswith('_') - # if the member __doc__ is the same as self's __doc__, it's just - # inherited and therefore not the member's doc - cls = self.get_attr(member, '__class__', None) - if cls: - cls_doc = self.get_attr(cls, '__doc__', None) - if cls_doc == doc: - doc = None - has_doc = bool(doc) - - keep = False - if want_all and membername.startswith('__') and \ - membername.endswith('__') and len(membername) > 4: - # special __methods__ - if self.options.special_members is ALL and \ - membername != '__doc__': - keep = has_doc or self.options.undoc_members - elif self.options.special_members and \ - self.options.special_members is not ALL and \ - membername in self.options.special_members: - keep = has_doc or self.options.undoc_members - elif (namespace, membername) in attr_docs: - if want_all and membername.startswith('_'): - # ignore members whose name starts with _ by default - keep = self.options.private_members + keep = False + if ismock(member) and (namespace, membername) not in attr_docs: + # mocked module or object + pass + elif (self.options.exclude_members and + membername in self.options.exclude_members): + # remove members given by exclude-members + keep = False + elif want_all and special_member_re.match(membername): + # special __methods__ + if (self.options.special_members and + membername in self.options.special_members): + if membername == '__doc__': + keep = False + elif is_filtered_inherited_member(membername, obj): + keep = False + else: + keep = has_doc or self.options.undoc_members + else: + keep = False + elif (namespace, membername) in attr_docs: + if want_all and isprivate: + if self.options.private_members is None: + keep = False + else: + keep = membername in self.options.private_members + else: + # keep documented attributes + keep = True + elif want_all and isprivate: + if has_doc or self.options.undoc_members: + if self.options.private_members is None: + keep = False + elif is_filtered_inherited_member(membername, obj): + keep = False + else: + keep = membername in self.options.private_members + else: + keep = False else: - # keep documented attributes - keep = True - isattr = True - elif want_all and membername.startswith('_'): - # ignore members whose name starts with _ by default - keep = self.options.private_members and \ - (has_doc or self.options.undoc_members) - else: - # ignore undocumented members if :undoc-members: is not given - keep = has_doc or self.options.undoc_members + if (self.options.members is ALL and + is_filtered_inherited_member(membername, obj)): + keep = False + else: + # ignore undocumented members if :undoc-members: is not given + keep = has_doc or self.options.undoc_members + + if isinstance(obj, ObjectMember) and obj.skipped: + # forcedly skipped member (ex. a module attribute not defined in __all__) + keep = False - # give the user a chance to decide whether this member - # should be skipped - if self.env.app: - # let extensions preprocess docstrings - try: + # give the user a chance to decide whether this member + # should be skipped + if self.env.app: + # let extensions preprocess docstrings skip_user = self.env.app.emit_firstresult( 'autodoc-skip-member', self.objtype, membername, member, not keep, self.options) if skip_user is not None: keep = not skip_user - except Exception as exc: - logger.warning(__('autodoc: failed to determine %r to be documented.' - 'the following exception was raised:\n%s'), - member, exc) - keep = False + except Exception as exc: + logger.warning(__('autodoc: failed to determine %s.%s (%r) to be documented, ' + 'the following exception was raised:\n%s'), + self.name, membername, member, exc, type='autodoc') + keep = False if keep: ret.append((membername, member, isattr)) return ret - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. - If *all_members* is True, do all members, else those given by + If *all_members* is True, document all members, else those given by *self.options.members*. """ # set current namespace for finding members @@ -684,18 +846,14 @@ def document_members(self, all_members=False): if self.objpath: self.env.temp_data['autodoc:class'] = self.objpath[0] - want_all = all_members or self.options.inherited_members or \ - self.options.members is ALL + want_all = (all_members or + self.options.inherited_members or + self.options.members is ALL) # find out which members are documentable members_check_module, members = self.get_object_members(want_all) - # remove members given by exclude-members - if self.options.exclude_members: - members = [(membername, member) for (membername, member) in members - if membername not in self.options.exclude_members] - # document non-skipped members - memberdocumenters = [] # type: List[Tuple[Documenter, bool]] + memberdocumenters: List[Tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -706,25 +864,12 @@ def document_members(self, all_members=False): classes.sort(key=lambda cls: cls.priority) # give explicitly separated module name, so that members # of inner classes can be documented - full_mname = self.modname + '::' + \ - '.'.join(self.objpath + [mname]) + full_mname = self.modname + '::' + '.'.join(self.objpath + [mname]) documenter = classes[-1](self.directive, full_mname, self.indent) memberdocumenters.append((documenter, isattr)) - member_order = self.options.member_order or \ - self.env.config.autodoc_member_order - if member_order == 'groupwise': - # sort by group; relies on stable sort to keep items in the - # same group sorted alphabetically - memberdocumenters.sort(key=lambda e: e[0].member_order) - elif member_order == 'bysource' and self.analyzer: - # sort by source order, by virtue of the module analyzer - tagorder = self.analyzer.tagorder - - def keyfunc(entry): - # type: (Tuple[Documenter, bool]) -> int - fullname = entry[0].name.split('::')[1] - return tagorder.get(fullname, len(tagorder)) - memberdocumenters.sort(key=keyfunc) + + member_order = self.options.member_order or self.config.autodoc_member_order + memberdocumenters = self.sort_members(memberdocumenters, member_order) for documenter, isattr in memberdocumenters: documenter.generate( @@ -735,9 +880,33 @@ def keyfunc(entry): self.env.temp_data['autodoc:module'] = None self.env.temp_data['autodoc:class'] = None - def generate(self, more_content=None, real_modname=None, - check_module=False, all_members=False): - # type: (Any, str, bool, bool) -> None + def sort_members(self, documenters: List[Tuple["Documenter", bool]], + order: str) -> List[Tuple["Documenter", bool]]: + """Sort the given member list.""" + if order == 'groupwise': + # sort by group; alphabetically within groups + documenters.sort(key=lambda e: (e[0].member_order, e[0].name)) + elif order == 'bysource': + if self.analyzer: + # sort by source order, by virtue of the module analyzer + tagorder = self.analyzer.tagorder + + def keyfunc(entry: Tuple[Documenter, bool]) -> int: + fullname = entry[0].name.split('::')[1] + return tagorder.get(fullname, len(tagorder)) + documenters.sort(key=keyfunc) + else: + # Assume that member discovery order matches source order. + # This is a reasonable assumption in Python 3.6 and up, where + # module.__dict__ is insertion-ordered. + pass + else: # alphabetical + documenters.sort(key=lambda e: e[0].name) + + return documenters + + def generate(self, more_content: Optional[StringList] = None, real_modname: str = None, + check_module: bool = False, all_members: bool = False) -> None: """Generate reST for the object given by *self.name*, and possibly for its members. @@ -749,10 +918,10 @@ def generate(self, more_content=None, real_modname=None, if not self.parse_name(): # need a module to import logger.warning( - 'don\'t know which module to import for autodocumenting ' - '%r (try placing a "module" or "currentmodule" directive ' - 'in the document, or giving an explicit module name)' % - self.name) + __('don\'t know which module to import for autodocumenting ' + '%r (try placing a "module" or "currentmodule" directive ' + 'in the document, or giving an explicit module name)') % + self.name, type='autodoc') return # now, import the module and get object to document @@ -764,7 +933,8 @@ def generate(self, more_content=None, real_modname=None, # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. - self.real_modname = real_modname or self.get_real_modname() # type: str + guess_modname = self.get_real_modname() + self.real_modname: str = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: @@ -772,25 +942,29 @@ def generate(self, more_content=None, real_modname=None, # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) self.analyzer.find_attr_docs() - except PycodeError as err: - logger.debug('[autodoc] module analyzer failed: %s', err) + except PycodeError as exc: + logger.debug('[autodoc] module analyzer failed: %s', exc) # no source file -- e.g. for builtin and C modules self.analyzer = None - # at least add the module as a dependency - name = self.module.__name__ if hasattr(self.module, '__name__') else None - fname = self.module.__file__ if hasattr(self.module, '__file__') else None - if name != self.real_modname: - # !!! SageMath Specific for make fast-rebuild-clean !!! - # try not to record a dependency to a .pyc file but to the corresponding .py files instead. - try: - fname = ModuleAnalyzer.for_module(name).srcname - except PycodeError: - pass - if fname is not None: - self.directive.record_dependencies.add(fname) + # at least add the module.__file__ as a dependency + if hasattr(self.module, '__file__') and self.module.__file__: + self.directive.record_dependencies.add(self.module.__file__) else: self.directive.record_dependencies.add(self.analyzer.srcname) + if self.real_modname != guess_modname: + # Add module to dependency list if target object is defined in other module. + try: + analyzer = ModuleAnalyzer.for_module(guess_modname) + self.directive.record_dependencies.add(analyzer.srcname) + except PycodeError: + pass + + docstrings: List[str] = sum(self.get_doc() or [], []) + if ismock(self.object) and not docstrings: + logger.warning(__('A mocked object is detected: %r'), + self.name, type='autodoc') + # check __module__ of object (for members not given explicitly) if check_module: if not self.check_module(): @@ -804,7 +978,12 @@ def generate(self, more_content=None, real_modname=None, self.add_line('', sourcename) # format the object's signature, if any - sig = self.format_signature() + try: + sig = self.format_signature() + except Exception as exc: + logger.warning(__('error while formatting signature for %s: %s'), + self.fullname, exc, type='autodoc') + return # generate the directive header and options, if applicable self.add_directive_header(sig) @@ -825,114 +1004,154 @@ class ModuleDocumenter(Documenter): Specialized Documenter subclass for modules. """ objtype = 'module' - content_indent = u'' + content_indent = '' titles_allowed = True + _extra_indent = ' ' - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, - 'member-order': identity, 'exclude-members': members_set_option, - 'private-members': bool_option, 'special-members': members_option, - 'imported-members': bool_option, - } # type: Dict[unicode, Callable] + 'member-order': member_order_option, 'exclude-members': exclude_members_option, + 'private-members': members_option, 'special-members': members_option, + 'imported-members': bool_option, 'ignore-module-all': bool_option, + 'no-value': bool_option, + } + + def __init__(self, *args: Any) -> None: + super().__init__(*args) + merge_members_option(self.options) + self.__all__: Optional[Sequence[str]] = None + + def add_content(self, more_content: Optional[StringList]) -> None: + old_indent = self.indent + self.indent += self._extra_indent + super().add_content(None) + self.indent = old_indent + if more_content: + for line, src in zip(more_content.data, more_content.items): + self.add_line(line, src[0], src[1]) @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[unicode]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: if modname is not None: - logger.warning('"::" in automodule name doesn\'t make sense') + logger.warning(__('"::" in automodule name doesn\'t make sense'), + type='autodoc') return (path or '') + base, [] - def parse_name(self): - # type: () -> bool - ret = Documenter.parse_name(self) + def parse_name(self) -> bool: + ret = super().parse_name() if self.args or self.retann: - logger.warning('signature arguments or return annotation ' - 'given for automodule %s' % self.fullname) + logger.warning(__('signature arguments or return annotation ' + 'given for automodule %s') % self.fullname, + type='autodoc') + return ret + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + + try: + if not self.options.ignore_module_all: + self.__all__ = inspect.getall(self.object) + except ValueError as exc: + # invalid __all__ found. + logger.warning(__('__all__ should be a list of strings, not %r ' + '(in module %s) -- ignoring __all__') % + (exc.args[0], self.fullname), type='autodoc') + return ret - def add_directive_header(self, sig): - # type: (unicode) -> None + def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) sourcename = self.get_sourcename() # add some module-specific options if self.options.synopsis: - self.add_line( - u' :synopsis: ' + self.options.synopsis, sourcename) + self.add_line(' :synopsis: ' + self.options.synopsis, sourcename) if self.options.platform: - self.add_line( - u' :platform: ' + self.options.platform, sourcename) + self.add_line(' :platform: ' + self.options.platform, sourcename) if self.options.deprecated: - self.add_line(u' :deprecated:', sourcename) + self.add_line(' :deprecated:', sourcename) - def get_module_members(self): + def get_module_members(self) -> Dict[str, ObjectMember]: """Get members of target module.""" if self.analyzer: attr_docs = self.analyzer.attr_docs else: attr_docs = {} - members = {} # type: Dict[str, ObjectMember] + members: Dict[str, ObjectMember] = {} for name in dir(self.object): try: value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) docstring = attr_docs.get(('', name), []) members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) except AttributeError: continue # annotation only member (ex. attr: int) - try: - for name in inspect.getannotations(self.object): - if name not in members: - docstring = attr_docs.get(('', name), []) - members[name] = ObjectMember(name, INSTANCEATTR, - docstring="\n".join(docstring)) - except AttributeError: - pass + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) return members - def get_object_members(self, want_all): - # type: (bool) -> Tuple[bool, List[Tuple[unicode, object]]] + def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: members = self.get_module_members() if want_all: - if not hasattr(self.object, '__all__'): + if self.__all__ is None: # for implicit module members, check __module__ to avoid # documenting imported objects return True, list(members.values()) else: - memberlist = self.object.__all__ - # Sometimes __all__ is broken... - if not isinstance(memberlist, (list, tuple)) or not \ - all(isinstance(entry, str) for entry in memberlist): - logger.warning( - '__all__ should be a list of strings, not %r ' - '(in module %s) -- ignoring __all__' % - (memberlist, self.fullname)) - # fall back to all members - return True, list(members.values()) + for member in members.values(): + if member.__name__ not in self.__all__: + member.skipped = True + + return False, list(members.values()) else: memberlist = self.options.members or [] - ret = [] - for mname in memberlist: - if mname in members: - ret.append(members[mname]) - else: - logger.warning( - 'missing attribute mentioned in :members: or __all__: ' - 'module %s, attribute %s' % - (safe_getattr(self.object, '__name__', '???'), mname)) - return False, ret + ret = [] + for name in memberlist: + if name in members: + ret.append(members[name]) + else: + logger.warning(__('missing attribute mentioned in :members: option: ' + 'module %s, attribute %s') % + (safe_getattr(self.object, '__name__', '???'), name), + type='autodoc') + return False, ret + + def sort_members(self, documenters: List[Tuple["Documenter", bool]], + order: str) -> List[Tuple["Documenter", bool]]: + if order == 'bysource' and self.__all__: + # Sort alphabetically first (for members not listed on the __all__) + documenters.sort(key=lambda e: e[0].name) + + # Sort by __all__ + def keyfunc(entry: Tuple[Documenter, bool]) -> int: + name = entry[0].name.split('::')[1] + if self.__all__ and name in self.__all__: + return self.__all__.index(name) + else: + return len(self.__all__) + documenters.sort(key=keyfunc) + + return documenters + else: + return super().sort_members(documenters, order) class ModuleLevelDocumenter(Documenter): @@ -940,8 +1159,8 @@ class ModuleLevelDocumenter(Documenter): Specialized Documenter subclass for objects on module level (functions, classes, data/constants). """ - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[unicode]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: if modname is None: if path: modname = path.rstrip('.') @@ -961,8 +1180,8 @@ class ClassLevelDocumenter(Documenter): Specialized Documenter subclass for objects on class level (methods, attributes). """ - def resolve_name(self, modname, parents, path, base): - # type: (str, Any, str, Any) -> Tuple[str, List[unicode]] + def resolve_name(self, modname: str, parents: Any, path: str, base: Any + ) -> Tuple[str, List[str]]: if modname is None: if path: mod_cls = path.rstrip('.') @@ -978,7 +1197,7 @@ def resolve_name(self, modname, parents, path, base): # ... if still None, there's no way to know if mod_cls is None: return None, [] - modname, _, cls = mod_cls.rpartition('.') # type: ignore + modname, sep, cls = mod_cls.rpartition('.') parents = [cls] # if the module name is still missing, get it like above if not modname: @@ -989,57 +1208,82 @@ def resolve_name(self, modname, parents, path, base): return modname, parents + [base] -class DocstringSignatureMixin(): +class DocstringSignatureMixin: """ Mixin for FunctionDocumenter and MethodDocumenter to provide the feature of reading the signature from the docstring. """ - - def _find_signature(self, encoding=None): - # type: (unicode) -> Tuple[str, str] - docstrings = self.get_doc(encoding) + _new_docstrings: List[List[str]] = None + _signatures: List[str] = None + + def _find_signature(self) -> Tuple[str, str]: + # candidates of the object name + valid_names = [self.objpath[-1]] # type: ignore + if isinstance(self, ClassDocumenter): + valid_names.append('__init__') + if hasattr(self.object, '__mro__'): + valid_names.extend(cls.__name__ for cls in self.object.__mro__) + + docstrings = self.get_doc() + if docstrings is None: + return None, None self._new_docstrings = docstrings[:] + self._signatures = [] result = None for i, doclines in enumerate(docstrings): - # no lines in docstring, no match - if not doclines: - continue - # match first line of docstring against signature RE - match = py_ext_sig_re.match(doclines[0]) # type: ignore - if not match: - continue - exmod, path, base, args, retann = match.groups() - # the base name must match ours - valid_names = [self.objpath[-1]] # type: ignore - if isinstance(self, ClassDocumenter): - valid_names.append('__init__') - if hasattr(self.object, '__mro__'): - valid_names.extend(cls.__name__ for cls in self.object.__mro__) - if base not in valid_names: - continue - # re-prepare docstring to ignore more leading indentation - self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:])) - result = args, retann - # don't look any further - break + for j, line in enumerate(doclines): + if not line: + # no lines in docstring, no match + break + + if line.endswith('\\'): + line = line.rstrip('\\').rstrip() + + # match first line of docstring against signature RE + match = py_ext_sig_re.match(line) + if not match: + break + exmod, path, base, args, retann = match.groups() + + # the base name must match ours + if base not in valid_names: + break + + # re-prepare docstring to ignore more leading indentation + tab_width = self.directive.state.document.settings.tab_width # type: ignore + self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[j + 1:]), + tab_width) + + if result is None: + # first signature + result = args, retann + else: + # subsequent signatures + self._signatures.append("(%s) -> %s" % (args, retann)) + + if result: + # finish the loop when signature found + break + return result - def get_doc(self, encoding=None, ignore=1): - # type: (unicode, int) -> List[List[unicode]] - lines = getattr(self, '_new_docstrings', None) - if lines is not None: - return lines - return Documenter.get_doc(self, encoding, ignore) # type: ignore + def get_doc(self) -> List[List[str]]: + if self._new_docstrings is not None: + return self._new_docstrings + return super().get_doc() # type: ignore - def format_signature(self): - # type: () -> unicode - if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore + def format_signature(self, **kwargs: Any) -> str: + if self.args is None and self.config.autodoc_docstring_signature: # type: ignore # only act if a signature is not explicitly given already, and if # the feature is enabled result = self._find_signature() if result is not None: self.args, self.retann = result - return Documenter.format_signature(self) + sig = super().format_signature(**kwargs) # type: ignore + if self._signatures: + return "\n".join([sig] + self._signatures) + else: + return sig class DocstringStripSignatureMixin(DocstringSignatureMixin): @@ -1047,9 +1291,8 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin): Mixin for AttributeDocumenter to provide the feature of stripping any function signature from the docstring. """ - def format_signature(self): - # type: () -> unicode - if self.args is None and self.env.config.autodoc_docstring_signature: + def format_signature(self, **kwargs: Any) -> str: + if self.args is None and self.config.autodoc_docstring_signature: # type: ignore # only act if a signature is not explicitly given already, and if # the feature is enabled result = self._find_signature() @@ -1058,10 +1301,10 @@ def format_signature(self): # DocstringSignatureMixin.format_signature. # Documenter.format_signature use self.args value to format. _args, self.retann = result - return Documenter.format_signature(self) + return super().format_signature(**kwargs) -class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): +class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore """ Specialized Documenter subclass for functions. """ @@ -1069,136 +1312,289 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): member_order = 30 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool - # It can be documented if it is a genuine function. + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + # -------------------------------------------------------------------- + # supports functions, builtins but, unlike Sphinx' autodoc, + # does not support bound methods exported at the module level + if is_function_or_cython_function(member) or inspect.isbuiltin(member): + return True + # Trac #9976: It can be documented if it is a genuine function. # Often, a class instance has the same documentation as its class, - # and then we typically want to document the class and not the instance. - # However, there is an exception: CachedFunction(f) returns a class instance, - # whose doc string coincides with that of f and is thus different from - # that of the class CachedFunction. In that situation, we want that f is documented. - # This is part of trac #9976. - return (is_function_or_cython_function(member) or inspect.isbuiltin(member) - or (isclassinstance(member) - and sage_getdoc_original(member) != sage_getdoc_original(member.__class__))) - - # Trac #9976: This function has been rewritten to support the - # _sage_argspec_ attribute which makes it possible to get argument - # specification of decorated callables in documentation correct. - # See e.g. sage.misc.decorators.sage_wraps - def args_on_obj(self, obj): - if hasattr(obj, "_sage_argspec_"): - return obj._sage_argspec_() - if inspect.isbuiltin(obj) or \ - inspect.ismethoddescriptor(obj): - # cannot introspect arguments of a C function or method - # unless a function to do so is supplied - if self.env.config.autodoc_builtin_argspec: - argspec = self.env.config.autodoc_builtin_argspec(obj) - return argspec - else: + # and then we typically want to document the class and not the + # instance. However, there is an exception: CachedFunction(f) returns + # a class instance, whose doc string coincides with that of f and is + # thus different from that of the class CachedFunction. In that + # situation, we want that f is documented. + return (isclassinstance(member) and + sage_getdoc_original(member) != sage_getdoc_original(member.__class__)) + # -------------------------------------------------------------------- + + def format_args(self, **kwargs: Any) -> str: + if self.config.autodoc_typehints in ('none', 'description'): + kwargs.setdefault('show_annotation', False) + if self.config.autodoc_typehints_format == "short": + kwargs.setdefault('unqualified_typehints', True) + + try: + self.env.app.emit('autodoc-before-process-signature', self.object, False) + # -------------------------------------------- + # Trac #9976: Support the _sage_argspec_ attribute which makes it + # possible to get argument specification of decorated callables in + # documentation correct. See e.g. sage.misc.decorators.sage_wraps + obj = self.object + + if hasattr(obj, "_sage_argspec_"): + argspec = obj._sage_argspec_() + if inspect.isbuiltin(obj) or \ + inspect.ismethoddescriptor(obj): + # cannot introspect arguments of a C function or method + # unless a function to do so is supplied + argspec = sage_getargspec(obj) + argspec = sage_getargspec(obj) + if isclassinstance(obj) or inspect.isclass(obj): + # if a class should be documented as function, we try + # to use the constructor signature as function + # signature without the first argument. + if argspec is not None and argspec[0]: + del argspec[0][0] + + if argspec is None: return None - argspec = sage_getargspec(obj) - if isclassinstance(obj) or inspect.isclass(obj): - # if a class should be documented as function, we try - # to use the constructor signature as function - # signature without the first argument. - if argspec is not None and argspec[0]: - del argspec[0][0] - return argspec - - def format_args(self): - # type: () -> unicode - argspec = self.args_on_obj(self.object) - if argspec is None: + args = sage_formatargspec(*argspec) + # -------------------------------------------- + except TypeError as exc: + logger.warning(__("Failed to get a function signature for %s: %s"), + self.fullname, exc) return None - args = formatargspec(self.object, *argspec) - # escape backslashes for reST - args = args.replace('\\', '\\\\') + except ValueError: + args = '' + + if self.config.strip_signature_backslash: + # escape backslashes for reST + args = args.replace('\\', '\\\\') return args - def document_members(self, all_members=False): - # type: (bool) -> None + def document_members(self, all_members: bool = False) -> None: pass + def add_directive_header(self, sig: str) -> None: + sourcename = self.get_sourcename() + super().add_directive_header(sig) + + if inspect.iscoroutinefunction(self.object) or inspect.isasyncgenfunction(self.object): + self.add_line(' :async:', sourcename) + + def format_signature(self, **kwargs: Any) -> str: + if self.config.autodoc_typehints_format == "short": + kwargs.setdefault('unqualified_typehints', True) -class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): + sigs = [] + if (self.analyzer and + '.'.join(self.objpath) in self.analyzer.overloads and + self.config.autodoc_typehints != 'none'): + # Use signatures for overloaded functions instead of the implementation function. + overloaded = True + else: + overloaded = False + sig = super().format_signature(**kwargs) + sigs.append(sig) + + if inspect.is_singledispatch_function(self.object): + # append signature of singledispatch'ed functions + for typ, func in self.object.registry.items(): + if typ is object: + pass # default implementation. skipped. + else: + dispatchfunc = self.annotate_to_first_argument(func, typ) + if dispatchfunc: + documenter = FunctionDocumenter(self.directive, '') + documenter.object = dispatchfunc + documenter.objpath = [None] + sigs.append(documenter.format_signature()) + if overloaded: + actual = inspect.signature(self.object, + type_aliases=self.config.autodoc_type_aliases) + __globals__ = safe_getattr(self.object, '__globals__', {}) + for overload in self.analyzer.overloads.get('.'.join(self.objpath)): + overload = self.merge_default_value(actual, overload) + overload = evaluate_signature(overload, __globals__, + self.config.autodoc_type_aliases) + + sig = stringify_signature(overload, **kwargs) + sigs.append(sig) + + return "\n".join(sigs) + + def merge_default_value(self, actual: Signature, overload: Signature) -> Signature: + """Merge default values of actual implementation to the overload variants.""" + parameters = list(overload.parameters.values()) + for i, param in enumerate(parameters): + actual_param = actual.parameters.get(param.name) + if actual_param and param.default == '...': + parameters[i] = param.replace(default=actual_param.default) + + return overload.replace(parameters=parameters) + + def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]: + """Annotate type hint to the first argument of function if needed.""" + try: + sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases) + except TypeError as exc: + logger.warning(__("Failed to get a function signature for %s: %s"), + self.fullname, exc) + return None + except ValueError: + return None + + if len(sig.parameters) == 0: + return None + + def dummy(): + pass + + params = list(sig.parameters.values()) + if params[0].annotation is Parameter.empty: + params[0] = params[0].replace(annotation=typ) + try: + dummy.__signature__ = sig.replace(parameters=params) # type: ignore + return dummy + except (AttributeError, TypeError): + # failed to update signature (ex. built-in or extension types) + return None + + return func + + +class DecoratorDocumenter(FunctionDocumenter): + """ + Specialized Documenter subclass for decorator functions. + """ + objtype = 'decorator' + + # must be lower than FunctionDocumenter + priority = -1 + + def format_args(self, **kwargs: Any) -> Any: + args = super().format_args(**kwargs) + if ',' in args: + return args + else: + return None + + +# Types which have confusing metaclass signatures it would be best not to show. +# These are listed by name, rather than storing the objects themselves, to avoid +# needing to import the modules. +_METACLASS_CALL_BLACKLIST = [ + 'enum.EnumMeta.__call__', +] + + +# Types whose __new__ signature is a pass-through. +_CLASS_NEW_BLACKLIST = [ + 'typing.Generic.__new__', +] + + +class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore """ Specialized Documenter subclass for classes. """ objtype = 'class' member_order = 20 - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, - 'show-inheritance': bool_option, 'member-order': identity, - 'exclude-members': members_set_option, - 'private-members': bool_option, 'special-members': members_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, + 'show-inheritance': bool_option, 'member-order': member_order_option, + 'exclude-members': exclude_members_option, + 'private-members': members_option, 'special-members': members_option, + 'class-doc-from': class_doc_from_option, } + _signature_class: Any = None + _signature_method_name: str = None + + def __init__(self, *args: Any) -> None: + super().__init__(*args) + + if self.config.autodoc_class_signature == 'separated': + self.options = self.options.copy() + + # show __init__() method + if self.options.special_members is None: + self.options['special-members'] = ['__new__', '__init__'] + else: + self.options.special_members.append('__new__') + self.options.special_members.append('__init__') + + merge_members_option(self.options) + @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return isinstance(member, type) - def import_object(self): - # type: () -> Any - ret = ModuleLevelDocumenter.import_object(self) - # Objective: if the class is documented under another name, document it + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + # if the class is documented under another name, document it # as data/attribute - # - # Notes from trac #27692: - # - # For Python 3, we make use of self.object.__qualname__. - # - # Notes from trac #7448: - # - # The original goal of this was that if some class is aliased, the - # alias is generated as a link rather than duplicated. For example in - # - # class A: - # pass - # B = A - # - # Then B is an alias of A, and should be generated as such. - # - # The way it was solved is to compare the name under which the - # current class is found and the actual name of the class (stored in - # the attribute __name__): - # - # if hasattr(self.object, '__name__'): - # self.doc_as_attr = (self.objpath[-1] != self.object.__name__) - # else: - # self.doc_as_attr = True - # - # Now, to work around a pickling bug of nested class in Python, - # by using the metaclass NestedMetaclass, we change the attribute - # __name__ of the nested class. For example, in - # - # class A(metaclass=NestedMetaclass): - # class B(): - # pass - # - # the class B get its name changed to 'A.B'. Such dots '.' in names - # are not supposed to occur in normal python name. I use it to check - # if the class is a nested one and to compare its __name__ with its - # path. - # - # The original implementation as well as the new one here doesn't work - # if a class is aliased from a different place under the same name. For - # example, in the following, - # - # class A: - # pass - # class Container: - # A = A - # - # The nested copy Container.A is also documented. Actually, it seems - # that there is no way to solve this by introspection. I'll submbit - # this problem on sphinx trac. - # - # References: trac #5986, file sage/misc/nested_class.py if ret: + if hasattr(self.object, '__name__'): + self.doc_as_attr = (self.objpath[-1] != self.object.__name__) + # ------------------------------------------------------------------ + # Trac #27692: + # + # For Python 3, we make use of self.object.__qualname__. + # + # Notes from trac #7448: + # + # The original goal of this was that if some class is aliased, the + # alias is generated as a link rather than duplicated. For example in + # + # class A: + # pass + # B = A + # + # Then B is an alias of A, and should be generated as such. + # + # The way it was solved is to compare the name under which the + # current class is found and the actual name of the class (stored in + # the attribute __name__): + # + # if hasattr(self.object, '__name__'): + # self.doc_as_attr = (self.objpath[-1] != self.object.__name__) + # else: + # self.doc_as_attr = True + # + # Now, to work around a pickling bug of nested class in Python, + # by using the metaclass NestedMetaclass, we change the attribute + # __name__ of the nested class. For example, in + # + # class A(metaclass=NestedMetaclass): + # class B(): + # pass + # + # the class B get its name changed to 'A.B'. Such dots '.' in names + # are not supposed to occur in normal python name. I use it to check + # if the class is a nested one and to compare its __name__ with its + # path. + # + # The original implementation as well as the new one here doesn't work + # if a class is aliased from a different place under the same name. For + # example, in the following, + # + # class A: + # pass + # class Container: + # A = A + # + # The nested copy Container.A is also documented. Actually, it seems + # that there is no way to solve this by introspection. I'll submbit + # this problem on sphinx trac. + # + # References: trac #5986, file sage/misc/nested_class.py + import sys module = getattr(self.object, '__module__', False) name = getattr(self.object, '__name__', False) qualname = getattr(self.object, '__qualname__', name) @@ -1212,135 +1608,344 @@ def import_object(self): cls = getattr(cls, part, None) self.doc_as_attr = (self.objpath != qualname_parts and self.object is cls) + # ------------------------------------------------------------------ else: self.doc_as_attr = True return ret - def format_args(self): - # type: () -> unicode - # for classes, the relevant signature is the __init__ method's - initmeth = self.get_attr(self.object, '__init__', None) - # classes without __init__ method, default __init__ or - # __init__ written in C? - if initmeth is None or \ - is_builtin_class_method(self.object, '__init__') or \ - not(inspect.ismethod(initmeth) or is_function_or_cython_function(initmeth)): - return None - try: - argspec = sage_getargspec(initmeth) - if argspec[0] and argspec[0][0] in ('cls', 'self'): - del argspec[0][0] - return formatargspec(initmeth, *argspec) - except TypeError: - # still not possible: happens e.g. for old-style classes - # with __init__ in C - return None + def _get_signature(self) -> Tuple[Optional[Any], Optional[str], Optional[Signature]]: + def get_user_defined_function_or_method(obj: Any, attr: str) -> Any: + """ Get the `attr` function or method from `obj`, if it is user-defined. """ + if inspect.is_builtin_class_method(obj, attr): + return None + attr = self.get_attr(obj, attr, None) + if not (inspect.ismethod(attr) or inspect.isfunction(attr)): + return None + return attr - def format_signature(self): - # type: () -> unicode - if self.doc_as_attr: - return '' + # This sequence is copied from inspect._signature_from_callable. + # ValueError means that no signature could be found, so we keep going. - return DocstringSignatureMixin.format_signature(self) + # First, we check the obj has a __signature__ attribute + if (hasattr(self.object, '__signature__') and + isinstance(self.object.__signature__, Signature)): + return None, None, self.object.__signature__ - def add_directive_header(self, sig): - # type: (unicode) -> None - if self.doc_as_attr: - self.directivetype = 'attribute' - Documenter.add_directive_header(self, sig) + # Next, let's see if it has an overloaded __call__ defined + # in its metaclass + call = get_user_defined_function_or_method(type(self.object), '__call__') - # add inheritance info, if wanted - if not self.doc_as_attr and self.options.show_inheritance: - sourcename = self.get_sourcename() - self.add_line(u'', sourcename) - if hasattr(self.object, '__bases__') and len(self.object.__bases__): - bases = [b.__module__ in ('__builtin__', 'builtins') and - u':class:`%s`' % b.__name__ or - u':class:`%s.%s`' % (b.__module__, b.__name__) - for b in self.object.__bases__] - self.add_line(u' ' + _(u'Bases: %s') % ', '.join(bases), - sourcename) - - def get_doc(self, encoding=None, ignore=1): - # type: (unicode, int) -> List[List[unicode]] - lines = getattr(self, '_new_docstrings', None) - if lines is not None: - return lines + if call is not None: + if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST: + call = None - content = self.env.config.autoclass_content + if call is not None: + self.env.app.emit('autodoc-before-process-signature', call, True) + try: + sig = inspect.signature(call, bound_method=True, + type_aliases=self.config.autodoc_type_aliases) + return type(self.object), '__call__', sig + except ValueError: + pass - docstrings = [] - attrdocstring = sage_getdoc_original(self.object) - if attrdocstring: - docstrings.append(attrdocstring) + # Now we check if the 'obj' class has a '__new__' method + new = get_user_defined_function_or_method(self.object, '__new__') - # for classes, what the "docstring" is can be controlled via a - # config value; the default is only the class docstring - if content in ('both', 'init'): - initdocstring = sage_getdoc_original( - self.get_attr(self.object, '__init__', None)) - # for new-style classes, no __init__ means default __init__ - if (initdocstring is not None and - (initdocstring == object.__init__.__doc__ or # for pypy - initdocstring.strip() == object.__init__.__doc__)): # for !pypy - initdocstring = None - if not initdocstring: - # try __new__ - initdocstring = self.get_attr( - self.get_attr(self.object, '__new__', None), '__doc__') - # for new-style classes, no __new__ means default __new__ - if (initdocstring is not None and - (initdocstring == object.__new__.__doc__ or # for pypy - initdocstring.strip() == object.__new__.__doc__)): # for !pypy - initdocstring = None - if initdocstring: - if content == 'init': - docstrings = [initdocstring] - else: - docstrings.append(initdocstring) - doc = [] - for docstring in docstrings: - if isinstance(docstring, str): - doc.append(prepare_docstring(docstring)) - return doc - - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None - if self.doc_as_attr: - # We cannot rely on __qualname__ yet for Python 2, because of a - # Cython bug: https://github.com/cython/cython/issues/2772. See - # trac #27002. - classname = safe_getattr(self.object, '__qualname__', None) - if not classname: - classname = safe_getattr(self.object, '__name__', None) - if classname: - module = safe_getattr(self.object, '__module__', None) - parentmodule = safe_getattr(self.parent, '__module__', None) - if module and module != parentmodule: - classname = str(module) + u'.' + str(classname) - content = ViewList( - [_('alias of :class:`%s`') % classname], source='') - ModuleLevelDocumenter.add_content(self, content, - no_docstring=True) - else: - ModuleLevelDocumenter.add_content(self, more_content) + if new is not None: + if "{0.__module__}.{0.__qualname__}".format(new) in _CLASS_NEW_BLACKLIST: + new = None - def document_members(self, all_members=False): - # type: (bool) -> None - if self.doc_as_attr: - return - ModuleLevelDocumenter.document_members(self, all_members) + if new is not None: + self.env.app.emit('autodoc-before-process-signature', new, True) + try: + sig = inspect.signature(new, bound_method=True, + type_aliases=self.config.autodoc_type_aliases) + return self.object, '__new__', sig + except ValueError: + pass - def generate(self, more_content=None, real_modname=None, - check_module=False, all_members=False): - # Do not pass real_modname and use the name from the __module__ + # Finally, we should have at least __init__ implemented + init = get_user_defined_function_or_method(self.object, '__init__') + if init is not None: + self.env.app.emit('autodoc-before-process-signature', init, True) + try: + sig = inspect.signature(init, bound_method=True, + type_aliases=self.config.autodoc_type_aliases) + return self.object, '__init__', sig + except ValueError: + pass + + # None of the attributes are user-defined, so fall back to let inspect + # handle it. + # We don't know the exact method that inspect.signature will read + # the signature from, so just pass the object itself to our hook. + self.env.app.emit('autodoc-before-process-signature', self.object, False) + try: + sig = inspect.signature(self.object, bound_method=False, + type_aliases=self.config.autodoc_type_aliases) + return None, None, sig + except ValueError: + pass + + # Still no signature: happens e.g. for old-style classes + # with __init__ in C and no `__text_signature__`. + return None, None, None + + def format_args(self, **kwargs: Any) -> str: + if self.config.autodoc_typehints in ('none', 'description'): + kwargs.setdefault('show_annotation', False) + if self.config.autodoc_typehints_format == "short": + kwargs.setdefault('unqualified_typehints', True) + + try: + self._signature_class, self._signature_method_name, sig = self._get_signature() + except TypeError as exc: + # __signature__ attribute contained junk + logger.warning(__("Failed to get a constructor signature for %s: %s"), + self.fullname, exc) + return None + + if sig is None: + return None + + return stringify_signature(sig, show_return_annotation=False, **kwargs) + + def _find_signature(self) -> Tuple[str, str]: + result = super()._find_signature() + if result is not None: + # Strip a return value from signature of constructor in docstring (first entry) + result = (result[0], None) + + for i, sig in enumerate(self._signatures): + if sig.endswith(' -> None'): + # Strip a return value from signatures of constructor in docstring (subsequent + # entries) + self._signatures[i] = sig[:-8] + + return result + + def format_signature(self, **kwargs: Any) -> str: + if self.doc_as_attr: + return '' + if self.config.autodoc_class_signature == 'separated': + # do not show signatures + return '' + + if self.config.autodoc_typehints_format == "short": + kwargs.setdefault('unqualified_typehints', True) + + sig = super().format_signature() + sigs = [] + + overloads = self.get_overloaded_signatures() + if overloads and self.config.autodoc_typehints != 'none': + # Use signatures for overloaded methods instead of the implementation method. + method = safe_getattr(self._signature_class, self._signature_method_name, None) + __globals__ = safe_getattr(method, '__globals__', {}) + for overload in overloads: + overload = evaluate_signature(overload, __globals__, + self.config.autodoc_type_aliases) + + parameters = list(overload.parameters.values()) + overload = overload.replace(parameters=parameters[1:], + return_annotation=Parameter.empty) + sig = stringify_signature(overload, **kwargs) + sigs.append(sig) + else: + sigs.append(sig) + + return "\n".join(sigs) + + def get_overloaded_signatures(self) -> List[Signature]: + if self._signature_class and self._signature_method_name: + for cls in self._signature_class.__mro__: + try: + analyzer = ModuleAnalyzer.for_module(cls.__module__) + analyzer.analyze() + qualname = '.'.join([cls.__qualname__, self._signature_method_name]) + if qualname in analyzer.overloads: + return analyzer.overloads.get(qualname) + elif qualname in analyzer.tagorder: + # the constructor is defined in the class, but not overridden. + return [] + except PycodeError: + pass + + return [] + + def get_canonical_fullname(self) -> Optional[str]: + __modname__ = safe_getattr(self.object, '__module__', self.modname) + __qualname__ = safe_getattr(self.object, '__qualname__', None) + if __qualname__ is None: + __qualname__ = safe_getattr(self.object, '__name__', None) + if __qualname__ and '' in __qualname__: + # No valid qualname found if the object is defined as locals + __qualname__ = None + + if __modname__ and __qualname__: + return '.'.join([__modname__, __qualname__]) + else: + return None + + def add_directive_header(self, sig: str) -> None: + sourcename = self.get_sourcename() + + if self.doc_as_attr: + self.directivetype = 'attribute' + super().add_directive_header(sig) + + if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: + self.add_line(' :final:', sourcename) + + canonical_fullname = self.get_canonical_fullname() + if not self.doc_as_attr and canonical_fullname and self.fullname != canonical_fullname: + self.add_line(' :canonical: %s' % canonical_fullname, sourcename) + + # add inheritance info, if wanted + if not self.doc_as_attr and self.options.show_inheritance: + if inspect.getorigbases(self.object): + # A subclass of generic types + # refs: PEP-560 + bases = list(self.object.__orig_bases__) + elif hasattr(self.object, '__bases__') and len(self.object.__bases__): + # A normal class + bases = list(self.object.__bases__) + else: + bases = [] + + self.env.events.emit('autodoc-process-bases', + self.fullname, self.object, self.options, bases) + + if self.config.autodoc_typehints_format == "short": + base_classes = [restify(cls, "smart") for cls in bases] + else: + base_classes = [restify(cls) for cls in bases] + + sourcename = self.get_sourcename() + self.add_line('', sourcename) + self.add_line(' ' + _('Bases: %s') % ', '.join(base_classes), sourcename) + + def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: + members = get_class_members(self.object, self.objpath, self.get_attr, + self.config.autodoc_inherit_docstrings) + if not want_all: + if not self.options.members: + return False, [] # type: ignore + # specific members given + selected = [] + for name in self.options.members: + if name in members: + selected.append(members[name]) + else: + logger.warning(__('missing attribute %s in object %s') % + (name, self.fullname), type='autodoc') + return False, selected + elif self.options.inherited_members: + return False, list(members.values()) + else: + return False, [m for m in members.values() if m.class_ == self.object] + + def get_doc(self) -> Optional[List[List[str]]]: + if self.doc_as_attr: + # Don't show the docstring of the class when it is an alias. + comment = self.get_variable_comment() + if comment: + return [] + else: + return None + + lines = getattr(self, '_new_docstrings', None) + if lines is not None: + return lines + + classdoc_from = self.options.get('class-doc-from', self.config.autoclass_content) + + docstrings = [] + attrdocstring = getdoc(self.object, self.get_attr) + if attrdocstring: + docstrings.append(attrdocstring) + + # for classes, what the "docstring" is can be controlled via a + # config value; the default is only the class docstring + if classdoc_from in ('both', 'init'): + __init__ = self.get_attr(self.object, '__init__', None) + initdocstring = getdoc(__init__, self.get_attr, + self.config.autodoc_inherit_docstrings, + self.object, '__init__') + # for new-style classes, no __init__ means default __init__ + if (initdocstring is not None and + (initdocstring == object.__init__.__doc__ or # for pypy + initdocstring.strip() == object.__init__.__doc__)): # for !pypy + initdocstring = None + if not initdocstring: + # try __new__ + __new__ = self.get_attr(self.object, '__new__', None) + initdocstring = getdoc(__new__, self.get_attr, + self.config.autodoc_inherit_docstrings, + self.object, '__new__') + # for new-style classes, no __new__ means default __new__ + if (initdocstring is not None and + (initdocstring == object.__new__.__doc__ or # for pypy + initdocstring.strip() == object.__new__.__doc__)): # for !pypy + initdocstring = None + if initdocstring: + if classdoc_from == 'init': + docstrings = [initdocstring] + else: + docstrings.append(initdocstring) + + tab_width = self.directive.state.document.settings.tab_width + return [prepare_docstring(docstring, tab_width) for docstring in docstrings] + + def get_variable_comment(self) -> Optional[List[str]]: + try: + key = ('', '.'.join(self.objpath)) + if self.doc_as_attr: + analyzer = ModuleAnalyzer.for_module(self.modname) + else: + analyzer = ModuleAnalyzer.for_module(self.get_real_modname()) + analyzer.analyze() + return list(analyzer.attr_docs.get(key, [])) + except PycodeError: + return None + + def add_content(self, more_content: Optional[StringList]) -> None: + if self.doc_as_attr and self.modname != self.get_real_modname(): + try: + # override analyzer to obtain doccomment around its definition. + self.analyzer = ModuleAnalyzer.for_module(self.modname) + self.analyzer.analyze() + except PycodeError: + pass + + if self.doc_as_attr and not self.get_variable_comment(): + try: + if self.config.autodoc_typehints_format == "short": + alias = restify(self.object, "smart") + else: + alias = restify(self.object) + more_content = StringList([_('alias of %s') % alias], source='') + except AttributeError: + pass # Invalid class object is passed. + + super().add_content(more_content) + + def document_members(self, all_members: bool = False) -> None: + if self.doc_as_attr: + return + super().document_members(all_members) + + def generate(self, more_content: Optional[StringList] = None, real_modname: str = None, + check_module: bool = False, all_members: bool = False) -> None: + # Do not pass real_modname and use the name from the __module__ # attribute of the class. # If a class gets imported into the module real_modname # the analyzer won't find the source of the class, if # it looks in real_modname. - return super(ClassDocumenter, self).generate(more_content=more_content, - check_module=check_module, - all_members=all_members) + return super().generate(more_content=more_content, + check_module=check_module, + all_members=all_members) + class ExceptionDocumenter(ClassDocumenter): """ @@ -1353,65 +1958,317 @@ class ExceptionDocumenter(ClassDocumenter): priority = 10 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return isinstance(member, type) and issubclass(member, BaseException) -class DataDocumenter(ModuleLevelDocumenter): +class DataDocumenterMixinBase: + # define types of instance variables + config: Config = None + env: BuildEnvironment = None + modname: str = None + parent: Any = None + object: Any = None + objpath: List[str] = None + + def should_suppress_directive_header(self) -> bool: + """Check directive header should be suppressed.""" + return False + + def should_suppress_value_header(self) -> bool: + """Check :value: header should be suppressed.""" + return False + + def update_content(self, more_content: StringList) -> None: + """Update docstring for the NewType object.""" + pass + + +class GenericAliasMixin(DataDocumenterMixinBase): + """ + Mixin for DataDocumenter and AttributeDocumenter to provide the feature for + supporting GenericAliases. + """ + + def should_suppress_directive_header(self) -> bool: + return (inspect.isgenericalias(self.object) or + super().should_suppress_directive_header()) + + def update_content(self, more_content: StringList) -> None: + if inspect.isgenericalias(self.object): + if self.config.autodoc_typehints_format == "short": + alias = restify(self.object, "smart") + else: + alias = restify(self.object) + + more_content.append(_('alias of %s') % alias, '') + more_content.append('', '') + + super().update_content(more_content) + + +class NewTypeMixin(DataDocumenterMixinBase): + """ + Mixin for DataDocumenter and AttributeDocumenter to provide the feature for + supporting NewTypes. + """ + + def should_suppress_directive_header(self) -> bool: + return (inspect.isNewType(self.object) or + super().should_suppress_directive_header()) + + def update_content(self, more_content: StringList) -> None: + if inspect.isNewType(self.object): + if self.config.autodoc_typehints_format == "short": + supertype = restify(self.object.__supertype__, "smart") + else: + supertype = restify(self.object.__supertype__) + + more_content.append(_('alias of %s') % supertype, '') + more_content.append('', '') + + super().update_content(more_content) + + +class TypeVarMixin(DataDocumenterMixinBase): + """ + Mixin for DataDocumenter and AttributeDocumenter to provide the feature for + supporting TypeVars. + """ + + def should_suppress_directive_header(self) -> bool: + return (isinstance(self.object, TypeVar) or + super().should_suppress_directive_header()) + + def get_doc(self) -> Optional[List[List[str]]]: + if isinstance(self.object, TypeVar): + if self.object.__doc__ != TypeVar.__doc__: + return super().get_doc() # type: ignore + else: + return [] + else: + return super().get_doc() # type: ignore + + def update_content(self, more_content: StringList) -> None: + if isinstance(self.object, TypeVar): + attrs = [repr(self.object.__name__)] + for constraint in self.object.__constraints__: + if self.config.autodoc_typehints_format == "short": + attrs.append(stringify_typehint(constraint, "smart")) + else: + attrs.append(stringify_typehint(constraint)) + if self.object.__bound__: + if self.config.autodoc_typehints_format == "short": + bound = restify(self.object.__bound__, "smart") + else: + bound = restify(self.object.__bound__) + attrs.append(r"bound=\ " + bound) + if self.object.__covariant__: + attrs.append("covariant=True") + if self.object.__contravariant__: + attrs.append("contravariant=True") + + more_content.append(_('alias of TypeVar(%s)') % ", ".join(attrs), '') + more_content.append('', '') + + super().update_content(more_content) + + +class UninitializedGlobalVariableMixin(DataDocumenterMixinBase): + """ + Mixin for DataDocumenter to provide the feature for supporting uninitialized + (type annotation only) global variables. + """ + + def import_object(self, raiseerror: bool = False) -> bool: + try: + return super().import_object(raiseerror=True) # type: ignore + except ImportError as exc: + # annotation only instance variable (PEP-526) + try: + with mock(self.config.autodoc_mock_imports): + parent = import_module(self.modname, self.config.autodoc_warningiserror) + annotations = get_type_hints(parent, None, + self.config.autodoc_type_aliases) + if self.objpath[-1] in annotations: + self.object = UNINITIALIZED_ATTR + self.parent = parent + return True + except ImportError: + pass + + if raiseerror: + raise + else: + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False + + def should_suppress_value_header(self) -> bool: + return (self.object is UNINITIALIZED_ATTR or + super().should_suppress_value_header()) + + def get_doc(self) -> Optional[List[List[str]]]: + if self.object is UNINITIALIZED_ATTR: + return [] + else: + return super().get_doc() # type: ignore + + +class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, + UninitializedGlobalVariableMixin, ModuleLevelDocumenter): """ Specialized Documenter subclass for data items. """ objtype = 'data' member_order = 40 priority = -10 - option_spec = dict(ModuleLevelDocumenter.option_spec) + option_spec: OptionSpec = dict(ModuleLevelDocumenter.option_spec) option_spec["annotation"] = annotation_option + option_spec["no-value"] = bool_option @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: return isinstance(parent, ModuleDocumenter) and isattr - def add_directive_header(self, sig): - # type: (unicode) -> None - ModuleLevelDocumenter.add_directive_header(self, sig) + def update_annotations(self, parent: Any) -> None: + """Update __annotations__ to support type_comment and so on.""" + annotations = dict(inspect.getannotations(parent)) + parent.__annotations__ = annotations + + try: + analyzer = ModuleAnalyzer.for_module(self.modname) + analyzer.analyze() + for (classname, attrname), annotation in analyzer.annotations.items(): + if classname == '' and attrname not in annotations: + annotations[attrname] = annotation + except PycodeError: + pass + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + if self.parent: + self.update_annotations(self.parent) + + return ret + + def should_suppress_value_header(self) -> bool: + if super().should_suppress_value_header(): + return True + else: + doc = self.get_doc() + docstring, metadata = separate_metadata('\n'.join(sum(doc, []))) + if 'hide-value' in metadata: + return True + + return False + + def add_directive_header(self, sig: str) -> None: + super().add_directive_header(sig) sourcename = self.get_sourcename() - if not self.options.annotation: + if self.options.annotation is SUPPRESS or self.should_suppress_directive_header(): + pass + elif self.options.annotation: + self.add_line(' :annotation: %s' % self.options.annotation, + sourcename) + else: + if self.config.autodoc_typehints != 'none': + # obtain annotation for this data + annotations = get_type_hints(self.parent, None, + self.config.autodoc_type_aliases) + if self.objpath[-1] in annotations: + if self.config.autodoc_typehints_format == "short": + objrepr = stringify_typehint(annotations.get(self.objpath[-1]), + "smart") + else: + objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + self.add_line(' :type: ' + objrepr, sourcename) + try: - objrepr = object_description(self.object) + if (self.options.no_value or self.should_suppress_value_header() or + ismock(self.object)): + pass + else: + objrepr = object_description(self.object) + self.add_line(' :value: ' + objrepr, sourcename) except ValueError: pass - else: - self.add_line(u' :annotation: = ' + objrepr, sourcename) - elif self.options.annotation is SUPPRESS: + + def document_members(self, all_members: bool = False) -> None: + pass + + def get_real_modname(self) -> str: + real_modname = self.get_attr(self.parent or self.object, '__module__', None) + return real_modname or self.modname + + def get_module_comment(self, attrname: str) -> Optional[List[str]]: + try: + analyzer = ModuleAnalyzer.for_module(self.modname) + analyzer.analyze() + key = ('', attrname) + if key in analyzer.attr_docs: + return list(analyzer.attr_docs[key]) + except PycodeError: pass + + return None + + def get_doc(self) -> Optional[List[List[str]]]: + # Check the variable has a docstring-comment + comment = self.get_module_comment(self.objpath[-1]) + if comment: + return [comment] else: - self.add_line(u' :annotation: %s' % self.options.annotation, - sourcename) + return super().get_doc() - def document_members(self, all_members=False): - # type: (bool) -> None - pass + def add_content(self, more_content: Optional[StringList]) -> None: + # Disable analyzing variable comment on Documenter.add_content() to control it on + # DataDocumenter.add_content() + self.analyzer = None + if not more_content: + more_content = StringList() -class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): + self.update_content(more_content) + super().add_content(more_content) + + +class NewTypeDataDocumenter(DataDocumenter): + """ + Specialized Documenter subclass for NewTypes. + + Note: This must be invoked before FunctionDocumenter because NewType is a kind of + function object. + """ + + objtype = 'newtypedata' + directivetype = 'data' + priority = FunctionDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return inspect.isNewType(member) and isattr + + +class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for methods (normal, static and class). """ objtype = 'method' + directivetype = 'method' member_order = 50 priority = 1 # must be more than FunctionDocumenter @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool - return inspect.isroutine(member) and not \ - isinstance(parent, ModuleDocumenter) - - def import_object(self): - # type: () -> Any - ret = ClassLevelDocumenter.import_object(self) + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return inspect.isroutine(member) and not isinstance(parent, ModuleDocumenter) + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) if not ret: return ret @@ -1420,195 +2277,643 @@ def import_object(self): if obj is None: obj = self.object - if isclassmethod(obj): - self.directivetype = 'classmethod' - # document class and static members before ordinary ones - self.member_order = self.member_order - 1 - elif isstaticmethod(obj, cls=self.parent, name=self.object_name): - self.directivetype = 'staticmethod' + if (inspect.isclassmethod(obj) or + inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name)): # document class and static members before ordinary ones self.member_order = self.member_order - 1 - else: - self.directivetype = 'method' + return ret - # Trac #9976: This function has been rewritten to support the - # _sage_argspec_ attribute which makes it possible to get argument - # specification of decorated callables in documentation correct. - # See e.g. sage.misc.decorators.sage_wraps. - # - # Note, however, that sage.misc.sageinspect.sage_getargspec already - # uses a method _sage_argspec_, that only works on objects, not on classes, though. - def args_on_obj(self, obj): + def format_args(self, **kwargs: Any) -> str: + if self.config.autodoc_typehints in ('none', 'description'): + kwargs.setdefault('show_annotation', False) + if self.config.autodoc_typehints_format == "short": + kwargs.setdefault('unqualified_typehints', True) + + # ----------------------------------------------------------------- + # Trac #9976: This function has been rewritten to support the + # _sage_argspec_ attribute which makes it possible to get argument + # specification of decorated callables in documentation correct. + # See e.g. sage.misc.decorators.sage_wraps. + # + # Note, however, that sage.misc.sageinspect.sage_getargspec already + # uses a method _sage_argspec_, that only works on objects, not on classes, though. + obj = self.object if hasattr(obj, "_sage_argspec_"): argspec = obj._sage_argspec_() elif inspect.isbuiltin(obj) or inspect.ismethoddescriptor(obj): # can never get arguments of a C function or method unless # a function to do so is supplied - if self.env.config.autodoc_builtin_argspec: - argspec = self.env.config.autodoc_builtin_argspec(obj) - else: - return None + argspec = sage_getargspec(obj) else: # The check above misses ordinary Python methods in Cython # files. argspec = sage_getargspec(obj) + if argspec is not None and argspec[0] and argspec[0][0] in ('cls', 'self'): del argspec[0][0] - return argspec - - def format_args(self): - # type: () -> unicode - argspec = self.args_on_obj(self.object) if argspec is None: return None - args = formatargspec(self.object, *argspec) - # escape backslashes for reST - args = args.replace('\\', '\\\\') + args = sage_formatargspec(*argspec) + # ----------------------------------------------------------------- + + if self.config.strip_signature_backslash: + # escape backslashes for reST + args = args.replace('\\', '\\\\') return args - def document_members(self, all_members=False): - # type: (bool) -> None + def add_directive_header(self, sig: str) -> None: + super().add_directive_header(sig) + + sourcename = self.get_sourcename() + obj = self.parent.__dict__.get(self.object_name, self.object) + if inspect.isabstractmethod(obj): + self.add_line(' :abstractmethod:', sourcename) + if inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj): + self.add_line(' :async:', sourcename) + if inspect.isclassmethod(obj): + self.add_line(' :classmethod:', sourcename) + if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name): + self.add_line(' :staticmethod:', sourcename) + if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: + self.add_line(' :final:', sourcename) + + def document_members(self, all_members: bool = False) -> None: pass -class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore + def merge_default_value(self, actual: Signature, overload: Signature) -> Signature: + """Merge default values of actual implementation to the overload variants.""" + parameters = list(overload.parameters.values()) + for i, param in enumerate(parameters): + actual_param = actual.parameters.get(param.name) + if actual_param and param.default == '...': + parameters[i] = param.replace(default=actual_param.default) + + return overload.replace(parameters=parameters) + + def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]: + """Annotate type hint to the first argument of function if needed.""" + try: + sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases) + except TypeError as exc: + logger.warning(__("Failed to get a method signature for %s: %s"), + self.fullname, exc) + return None + except ValueError: + return None + + if len(sig.parameters) == 1: + return None + + def dummy(): + pass + + params = list(sig.parameters.values()) + if params[1].annotation is Parameter.empty: + params[1] = params[1].replace(annotation=typ) + try: + dummy.__signature__ = sig.replace(parameters=params) # type: ignore + return dummy + except (AttributeError, TypeError): + # failed to update signature (ex. built-in or extension types) + return None + + return func + + def get_doc(self) -> Optional[List[List[str]]]: + if self._new_docstrings is not None: + # docstring already returned previously, then modified by + # `DocstringSignatureMixin`. Just return the previously-computed + # result, so that we don't lose the processing done by + # `DocstringSignatureMixin`. + return self._new_docstrings + if self.objpath[-1] == '__init__': + docstring = getdoc(self.object, self.get_attr, + self.config.autodoc_inherit_docstrings, + self.parent, self.object_name) + if (docstring is not None and + (docstring == object.__init__.__doc__ or # for pypy + docstring.strip() == object.__init__.__doc__)): # for !pypy + docstring = None + if docstring: + tab_width = self.directive.state.document.settings.tab_width + return [prepare_docstring(docstring, tabsize=tab_width)] + else: + return [] + elif self.objpath[-1] == '__new__': + docstring = getdoc(self.object, self.get_attr, + self.config.autodoc_inherit_docstrings, + self.parent, self.object_name) + if (docstring is not None and + (docstring == object.__new__.__doc__ or # for pypy + docstring.strip() == object.__new__.__doc__)): # for !pypy + docstring = None + if docstring: + tab_width = self.directive.state.document.settings.tab_width + return [prepare_docstring(docstring, tabsize=tab_width)] + else: + return [] + else: + return super().get_doc() + + +class NonDataDescriptorMixin(DataDocumenterMixinBase): + """ + Mixin for AttributeDocumenter to provide the feature for supporting non + data-descriptors. + + .. note:: This mix-in must be inherited after other mix-ins. Otherwise, docstring + and :value: header will be suppressed unexpectedly. + """ + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) # type: ignore + if ret and not inspect.isattributedescriptor(self.object): + self.non_data_descriptor = True + else: + self.non_data_descriptor = False + + return ret + + def should_suppress_value_header(self) -> bool: + return (not getattr(self, 'non_data_descriptor', False) or + super().should_suppress_directive_header()) + + def get_doc(self) -> Optional[List[List[str]]]: + if getattr(self, 'non_data_descriptor', False): + # the docstring of non datadescriptor is very probably the wrong thing + # to display + return None + else: + return super().get_doc() # type: ignore + + +class SlotsMixin(DataDocumenterMixinBase): + """ + Mixin for AttributeDocumenter to provide the feature for supporting __slots__. + """ + + def isslotsattribute(self) -> bool: + """Check the subject is an attribute in __slots__.""" + try: + __slots__ = inspect.getslots(self.parent) + if __slots__ and self.objpath[-1] in __slots__: + return True + else: + return False + except (ValueError, TypeError): + return False + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) # type: ignore + if self.isslotsattribute(): + self.object = SLOTSATTR + + return ret + + def should_suppress_value_header(self) -> bool: + if self.object is SLOTSATTR: + return True + else: + return super().should_suppress_value_header() + + def get_doc(self) -> Optional[List[List[str]]]: + if self.object is SLOTSATTR: + try: + __slots__ = inspect.getslots(self.parent) + if __slots__ and __slots__.get(self.objpath[-1]): + docstring = prepare_docstring(__slots__[self.objpath[-1]]) + return [docstring] + else: + return [] + except ValueError as exc: + logger.warning(__('Invalid __slots__ found on %s. Ignored.'), + (self.parent.__qualname__, exc), type='autodoc') + return [] + else: + return super().get_doc() # type: ignore + + @property + def _datadescriptor(self) -> bool: + warnings.warn('AttributeDocumenter._datadescriptor() is deprecated.', + RemovedInSphinx60Warning) + if self.object is SLOTSATTR: + return True + else: + return False + + +class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase): + """ + Mixin for AttributeDocumenter to provide the feature for supporting runtime + instance attributes (that are defined in __init__() methods with doc-comments). + + Example: + + class Foo: + def __init__(self): + self.attr = None #: This is a target of this mix-in. + """ + + RUNTIME_INSTANCE_ATTRIBUTE = object() + + def is_runtime_instance_attribute(self, parent: Any) -> bool: + """Check the subject is an attribute defined in __init__().""" + # An instance variable defined in __init__(). + if self.get_attribute_comment(parent, self.objpath[-1]): # type: ignore + return True + elif self.is_runtime_instance_attribute_not_commented(parent): + return True + else: + return False + + def is_runtime_instance_attribute_not_commented(self, parent: Any) -> bool: + """Check the subject is an attribute defined in __init__() without comment.""" + for cls in inspect.getmro(parent): + try: + module = safe_getattr(cls, '__module__') + qualname = safe_getattr(cls, '__qualname__') + + analyzer = ModuleAnalyzer.for_module(module) + analyzer.analyze() + if qualname and self.objpath: + key = '.'.join([qualname, self.objpath[-1]]) + if key in analyzer.tagorder: + return True + except (AttributeError, PycodeError): + pass + + return None + + def import_object(self, raiseerror: bool = False) -> bool: + """Check the existence of runtime instance attribute after failing to import the + attribute.""" + try: + return super().import_object(raiseerror=True) # type: ignore + except ImportError as exc: + try: + with mock(self.config.autodoc_mock_imports): + ret = import_object(self.modname, self.objpath[:-1], 'class', + attrgetter=self.get_attr, # type: ignore + warningiserror=self.config.autodoc_warningiserror) + parent = ret[3] + if self.is_runtime_instance_attribute(parent): + self.object = self.RUNTIME_INSTANCE_ATTRIBUTE + self.parent = parent + return True + except ImportError: + pass + + if raiseerror: + raise + else: + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False + + def should_suppress_value_header(self) -> bool: + return (self.object is self.RUNTIME_INSTANCE_ATTRIBUTE or + super().should_suppress_value_header()) + + def get_doc(self) -> Optional[List[List[str]]]: + if (self.object is self.RUNTIME_INSTANCE_ATTRIBUTE and + self.is_runtime_instance_attribute_not_commented(self.parent)): + return None + else: + return super().get_doc() # type: ignore + + +class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase): + """ + Mixin for AttributeDocumenter to provide the feature for supporting uninitialized + instance attributes (PEP-526 styled, annotation only attributes). + + Example: + + class Foo: + attr: int #: This is a target of this mix-in. + """ + + def is_uninitialized_instance_attribute(self, parent: Any) -> bool: + """Check the subject is an annotation only attribute.""" + annotations = get_type_hints(parent, None, self.config.autodoc_type_aliases) + if self.objpath[-1] in annotations: + return True + else: + return False + + def import_object(self, raiseerror: bool = False) -> bool: + """Check the exisitence of uninitialized instance attribute when failed to import + the attribute.""" + try: + return super().import_object(raiseerror=True) # type: ignore + except ImportError as exc: + try: + ret = import_object(self.modname, self.objpath[:-1], 'class', + attrgetter=self.get_attr, # type: ignore + warningiserror=self.config.autodoc_warningiserror) + parent = ret[3] + if self.is_uninitialized_instance_attribute(parent): + self.object = UNINITIALIZED_ATTR + self.parent = parent + return True + except ImportError: + pass + + if raiseerror: + raise + else: + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False + + def should_suppress_value_header(self) -> bool: + return (self.object is UNINITIALIZED_ATTR or + super().should_suppress_value_header()) + + def get_doc(self) -> Optional[List[List[str]]]: + if self.object is UNINITIALIZED_ATTR: + return None + else: + return super().get_doc() # type: ignore + + +class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: ignore + TypeVarMixin, RuntimeInstanceAttributeMixin, + UninitializedInstanceAttributeMixin, NonDataDescriptorMixin, + DocstringStripSignatureMixin, ClassLevelDocumenter): """ Specialized Documenter subclass for attributes. """ objtype = 'attribute' member_order = 60 - option_spec = dict(ModuleLevelDocumenter.option_spec) + option_spec: OptionSpec = dict(ModuleLevelDocumenter.option_spec) option_spec["annotation"] = annotation_option + option_spec["no-value"] = bool_option # must be higher than the MethodDocumenter, else it will recognize # some non-data descriptors as methods priority = 10 @staticmethod - def is_function_or_method(obj): - return is_function_or_cython_function(obj) or inspect.isbuiltin(obj) or inspect.ismethod(obj) + def is_function_or_method(obj: Any) -> bool: + return inspect.isfunction(obj) or inspect.isbuiltin(obj) or inspect.ismethod(obj) @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool - non_attr_types = (type, MethodDescriptorType) - isdatadesc = isdescriptor(member) and not \ - inspect.isroutine(member) and not \ - isinstance(member, non_attr_types) and not \ - type(member).__name__ == "instancemethod" - - isattribute = isdatadesc or (not isinstance(parent, ModuleDocumenter) and isattr) - - # Trac #26522: This condition is here just to pass objects of classes - # that inherit ClasscallMetaclass as attributes rather than method - # descriptors. - isattribute = isattribute or isinstance(type(member), ClasscallMetaclass) - - return isattribute - - # We ignore the obscure case supported in the following return - # statement. The additional check opens a door for attributes without - # docstrings to appear in the Sage documentation, and more seriously - # effectively prevents certain attributes to get properly documented. - # See trac #28698. - - # That last condition addresses an obscure case of C-defined - # methods using a deprecated type in Python 3, that is not otherwise - # exported anywhere by Python - return isattribute or (not isinstance(parent, ModuleDocumenter) and - not inspect.isroutine(member) and - not isinstance(member, type)) - - def document_members(self, all_members=False): - # type: (bool) -> None + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + if isinstance(parent, ModuleDocumenter): + return False + # ------------------------------------------------------------------- + # Trac: Do not pass objects of class CachedMethodCallerNoArgs as attributes. + # + # sage: from sphinx.util import inspect + # sage: A = AlgebrasWithBasis(QQ).example() + # sage: member = A.one_basis + # sage: type(member) + # + # sage: inspect.isattributedescriptor(member) + # True + # sage: inspect.isroutine(member) + # True + # + from sage.misc.classcall_metaclass import ClasscallMetaclass + if inspect.isattributedescriptor(member) and not inspect.isroutine(member): + return True + # Trac #26522: Pass objects of classes that inherit ClasscallMetaclass + # as attributes rather than method descriptors. + if isinstance(type(member), ClasscallMetaclass): + return True + # ------------------------------------------------------------------- + elif not inspect.isroutine(member) and not isinstance(member, type): + return True + else: + return False + + def document_members(self, all_members: bool = False) -> None: pass - def import_object(self): - # type: () -> Any - ret = ClassLevelDocumenter.import_object(self) - if isenumattribute(self.object): + def update_annotations(self, parent: Any) -> None: + """Update __annotations__ to support type_comment and so on.""" + try: + annotations = dict(inspect.getannotations(parent)) + parent.__annotations__ = annotations + + for cls in inspect.getmro(parent): + try: + module = safe_getattr(cls, '__module__') + qualname = safe_getattr(cls, '__qualname__') + + analyzer = ModuleAnalyzer.for_module(module) + analyzer.analyze() + for (classname, attrname), annotation in analyzer.annotations.items(): + if classname == qualname and attrname not in annotations: + annotations[attrname] = annotation + except (AttributeError, PycodeError): + pass + except (AttributeError, TypeError): + # Failed to set __annotations__ (built-in, extensions, etc.) + pass + + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + if inspect.isenumattribute(self.object): self.object = self.object.value - if isdescriptor(self.object) and \ - not self.is_function_or_method(self.object): - self._datadescriptor = True - else: - # if it's not a data descriptor - self._datadescriptor = False + if self.parent: + self.update_annotations(self.parent) + return ret - def get_real_modname(self): - # type: () -> str - return self.get_attr(self.parent or self.object, '__module__', None) \ - or self.modname + def get_real_modname(self) -> str: + real_modname = self.get_attr(self.parent or self.object, '__module__', None) + return real_modname or self.modname + + def should_suppress_value_header(self) -> bool: + if super().should_suppress_value_header(): + return True + else: + doc = self.get_doc() + if doc: + docstring, metadata = separate_metadata('\n'.join(sum(doc, []))) + if 'hide-value' in metadata: + return True + + return False - def add_directive_header(self, sig): - # type: (unicode) -> None - ClassLevelDocumenter.add_directive_header(self, sig) + def add_directive_header(self, sig: str) -> None: + super().add_directive_header(sig) sourcename = self.get_sourcename() - if not self.options.annotation: - if not self._datadescriptor: - try: - objrepr = object_description(self.object) - except ValueError: - pass - else: - self.add_line(u' :annotation: = ' + objrepr, sourcename) - elif self.options.annotation is SUPPRESS: + if self.options.annotation is SUPPRESS or self.should_suppress_directive_header(): pass + elif self.options.annotation: + self.add_line(' :annotation: %s' % self.options.annotation, sourcename) else: - self.add_line(u' :annotation: %s' % self.options.annotation, - sourcename) + if self.config.autodoc_typehints != 'none': + # obtain type annotation for this attribute + annotations = get_type_hints(self.parent, None, + self.config.autodoc_type_aliases) + if self.objpath[-1] in annotations: + if self.config.autodoc_typehints_format == "short": + objrepr = stringify_typehint(annotations.get(self.objpath[-1]), + "smart") + else: + objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + self.add_line(' :type: ' + objrepr, sourcename) - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None - if not self._datadescriptor: - # if it's not a data descriptor, its docstring is very probably the - # wrong thing to display - no_docstring = True - ClassLevelDocumenter.add_content(self, more_content, no_docstring) + try: + if (self.options.no_value or self.should_suppress_value_header() or + ismock(self.object)): + pass + else: + objrepr = object_description(self.object) + self.add_line(' :value: ' + objrepr, sourcename) + except ValueError: + pass + + def get_attribute_comment(self, parent: Any, attrname: str) -> Optional[List[str]]: + for cls in inspect.getmro(parent): + try: + module = safe_getattr(cls, '__module__') + qualname = safe_getattr(cls, '__qualname__') + + analyzer = ModuleAnalyzer.for_module(module) + analyzer.analyze() + if qualname and self.objpath: + key = (qualname, attrname) + if key in analyzer.attr_docs: + return list(analyzer.attr_docs[key]) + except (AttributeError, PycodeError): + pass + return None -class InstanceAttributeDocumenter(AttributeDocumenter): + def get_doc(self) -> Optional[List[List[str]]]: + # Check the attribute has a docstring-comment + comment = self.get_attribute_comment(self.parent, self.objpath[-1]) + if comment: + return [comment] + + try: + # Disable `autodoc_inherit_docstring` temporarily to avoid to obtain + # a docstring from the value which descriptor returns unexpectedly. + # ref: https://github.com/sphinx-doc/sphinx/issues/7805 + orig = self.config.autodoc_inherit_docstrings + self.config.autodoc_inherit_docstrings = False # type: ignore + return super().get_doc() + finally: + self.config.autodoc_inherit_docstrings = orig # type: ignore + + def add_content(self, more_content: Optional[StringList]) -> None: + # Disable analyzing attribute comment on Documenter.add_content() to control it on + # AttributeDocumenter.add_content() + self.analyzer = None + + if more_content is None: + more_content = StringList() + self.update_content(more_content) + super().add_content(more_content) + + +class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore """ - Specialized Documenter subclass for attributes that cannot be imported - because they are instance attributes (e.g. assigned in __init__). + Specialized Documenter subclass for properties. """ - objtype = 'instanceattribute' - directivetype = 'attribute' + objtype = 'property' member_order = 60 - # must be higher than AttributeDocumenter - priority = 11 + # before AttributeDocumenter + priority = AttributeDocumenter.priority + 1 @classmethod - def can_document_member(cls, member, membername, isattr, parent): - # type: (Any, unicode, bool, Any) -> bool - """This documents only INSTANCEATTR members.""" - return isattr and (member is INSTANCEATTR) - - def import_object(self): - # type: () -> bool - """Never import anything.""" - # disguise as an attribute - self.objtype = 'attribute' - self._datadescriptor = False - return True + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + if isinstance(parent, ClassDocumenter): + if inspect.isproperty(member): + return True + else: + __dict__ = safe_getattr(parent.object, '__dict__', {}) + obj = __dict__.get(membername) + return isinstance(obj, classmethod) and inspect.isproperty(obj.__func__) + else: + return False + + def import_object(self, raiseerror: bool = False) -> bool: + """Check the exisitence of uninitialized instance attribute when failed to import + the attribute.""" + ret = super().import_object(raiseerror) + if ret and not inspect.isproperty(self.object): + __dict__ = safe_getattr(self.parent, '__dict__', {}) + obj = __dict__.get(self.objpath[-1]) + if isinstance(obj, classmethod) and inspect.isproperty(obj.__func__): + self.object = obj.__func__ + self.isclassmethod = True + return True + else: + return False - def add_content(self, more_content, no_docstring=False): - # type: (Any, bool) -> None - """Never try to get a docstring from the object.""" - AttributeDocumenter.add_content(self, more_content, no_docstring=True) + self.isclassmethod = False + return ret + def document_members(self, all_members: bool = False) -> None: + pass -def get_documenters(app): - # type: (Sphinx) -> Dict[unicode, Type[Documenter]] - """Returns registered Documenter classes""" - return app.registry.documenters + def get_real_modname(self) -> str: + real_modname = self.get_attr(self.parent or self.object, '__module__', None) + return real_modname or self.modname + def add_directive_header(self, sig: str) -> None: + super().add_directive_header(sig) + sourcename = self.get_sourcename() + if inspect.isabstractmethod(self.object): + self.add_line(' :abstractmethod:', sourcename) + if self.isclassmethod: + self.add_line(' :classmethod:', sourcename) + + if safe_getattr(self.object, 'fget', None): # property + func = self.object.fget + elif safe_getattr(self.object, 'func', None): # cached_property + func = self.object.func + else: + func = None -def autodoc_attrgetter(app, obj, name, *defargs): - # type: (Sphinx, Any, unicode, Any) -> Any + if func and self.config.autodoc_typehints != 'none': + try: + signature = inspect.signature(func, + type_aliases=self.config.autodoc_type_aliases) + if signature.return_annotation is not Parameter.empty: + if self.config.autodoc_typehints_format == "short": + objrepr = stringify_typehint(signature.return_annotation, "smart") + else: + objrepr = stringify_typehint(signature.return_annotation) + self.add_line(' :type: ' + objrepr, sourcename) + except TypeError as exc: + logger.warning(__("Failed to get a function signature for %s: %s"), + self.fullname, exc) + return None + except ValueError: + return None + + +class NewTypeAttributeDocumenter(AttributeDocumenter): + """ + Specialized Documenter subclass for NewTypes. + + Note: This must be invoked before MethodDocumenter because NewType is a kind of + function object. + """ + + objtype = 'newvarattribute' + directivetype = 'attribute' + priority = MethodDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return not isinstance(parent, ModuleDocumenter) and inspect.isNewType(member) + + +def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: """Alternative getattr() for types""" for typ, func in app.registry.autodoc_attrgettrs.items(): if isinstance(obj, typ): @@ -1617,41 +2922,43 @@ def autodoc_attrgetter(app, obj, name, *defargs): return safe_getattr(obj, name, *defargs) -def setup(app): - # type: (Sphinx) -> Dict[unicode, Any] +def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ClassDocumenter) app.add_autodocumenter(ExceptionDocumenter) app.add_autodocumenter(DataDocumenter) + app.add_autodocumenter(NewTypeDataDocumenter) app.add_autodocumenter(FunctionDocumenter) + app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) - app.add_autodocumenter(InstanceAttributeDocumenter) + app.add_autodocumenter(PropertyDocumenter) + app.add_autodocumenter(NewTypeAttributeDocumenter) - app.add_config_value('autoclass_content', 'class', True) - app.add_config_value('autodoc_member_order', 'alphabetic', True) - app.add_config_value('autodoc_default_flags', [], True) # deprecated since Sphinx 1.8 + app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init')) + app.add_config_value('autodoc_member_order', 'alphabetical', True, + ENUM('alphabetical', 'bysource', 'groupwise')) + app.add_config_value('autodoc_class_signature', 'mixed', True, ENUM('mixed', 'separated')) app.add_config_value('autodoc_default_options', {}, True) - app.add_config_value('autodoc_docstring_signature', False, True) + app.add_config_value('autodoc_docstring_signature', True, True) app.add_config_value('autodoc_mock_imports', [], True) + app.add_config_value('autodoc_typehints', "signature", True, + ENUM("signature", "description", "none", "both")) + app.add_config_value('autodoc_typehints_description_target', 'all', True, + ENUM('all', 'documented', 'documented_params')) + app.add_config_value('autodoc_type_aliases', {}, True) + app.add_config_value('autodoc_typehints_format', "short", 'env', + ENUM("fully-qualified", "short")) app.add_config_value('autodoc_warningiserror', True, True) app.add_config_value('autodoc_inherit_docstrings', True, True) - app.add_config_value('autodoc_builtin_argspec', None, True) - + app.add_event('autodoc-before-process-signature') app.add_event('autodoc-process-docstring') app.add_event('autodoc-process-signature') app.add_event('autodoc-skip-member') + app.add_event('autodoc-process-bases') - return {'version': sphinx.__display_version__, 'parallel_read_safe': True} - + app.setup_extension('sphinx.ext.autodoc.preserve_defaults') + app.setup_extension('sphinx.ext.autodoc.type_comment') + app.setup_extension('sphinx.ext.autodoc.typehints') -class testcls: - """test doc string""" - - def __getattr__(self, x): - # type: (Any) -> Any - return x - - def __setattr__(self, x, y): - # type: (Any, Any) -> None - """Attr setter.""" + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/src/sage_setup/cython_options.py b/src/sage_setup/cython_options.py index 086aa070ca9..a878fb754e4 100644 --- a/src/sage_setup/cython_options.py +++ b/src/sage_setup/cython_options.py @@ -10,6 +10,11 @@ def compiler_directives(profile: bool): auto_pickle=False, # Do not create __test__ dictionary automatically from docstrings autotestdict=False, + # When enabled, functions will bind to an instance when looked up as a + # class attribute (hence the name) and will emulate the attributes of + # Python functions, including introspections like argument names and + # annotations + binding=True, # Do not check for division by 0 (this is about 35% quicker than with check) cdivision=True, # Embed a textual copy of the call signature in the docstring (to support tools like IPython) From f5fc398a2fcbae655942e8b075a65319e82c49ee Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 7 Nov 2022 21:17:29 +0800 Subject: [PATCH 357/414] copy orders between isogenous curves for all EllipticCurveHom instances --- .../elliptic_curves/ell_curve_isogeny.py | 6 --- src/sage/schemes/elliptic_curves/ell_field.py | 47 ------------------- .../elliptic_curves/ell_finite_field.py | 40 ++++++++++++++++ src/sage/schemes/elliptic_curves/hom.py | 33 +++++++++++++ 4 files changed, 73 insertions(+), 53 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index d03346deacc..141ef7ef1b1 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -1018,12 +1018,6 @@ def __init__(self, E, kernel, codomain=None, degree=None, model=None, check=True # Inheritance house keeping self.__perform_inheritance_housekeeping() - # over finite fields, isogenous curves have the same number - # of rational points, hence we copy over cached curve orders - if self.__base_field.is_finite(): - self._codomain._fetch_cached_order(self._domain) - self._domain._fetch_cached_order(self._codomain) - def _eval(self, P): r""" Less strict evaluation method for internal use. diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 6b64df4d075..2b38144b046 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -1024,53 +1024,6 @@ def division_field(self, l, names='t', map=False, **kwds): else: return L - def _fetch_cached_order(self, other): - r""" - This method copies the ``_order`` member from ``other`` - to ``self`` if their base field is the same and finite. - - This is used in :class:`EllipticCurveIsogeny` to keep track of - an already computed curve order: According to Tate's theorem - [Tate1966b]_, isogenous elliptic curves over a finite field - have the same number of rational points. - - EXAMPLES:: - - sage: E1 = EllipticCurve(GF(2^127-1), [1,2,3,4,5]) - sage: E1.set_order(170141183460469231746191640949390434666) - sage: E2 = EllipticCurve(GF(2^127-1), [115649500210559831225094148253060920818, 36348294106991415644658737184600079491]) - sage: E2._fetch_cached_order(E1) - sage: E2._order - 170141183460469231746191640949390434666 - - TESTS:: - - sage: E3 = EllipticCurve(GF(17), [1,2,3,4,5]) - sage: hasattr(E3, '_order') - False - sage: E3._fetch_cached_order(E1) - Traceback (most recent call last): - ... - ValueError: curves have distinct base fields - - :: - - sage: E4 = EllipticCurve([1,2,3,4,5]) - sage: E4._fetch_cached_order(E1.change_ring(QQ)) - sage: hasattr(E4, '_order') - False - """ - if hasattr(self, '_order') or not hasattr(other, '_order'): - return - F = self.base_field() - if F != other.base_field(): - raise ValueError('curves have distinct base fields') - if not F.is_finite(): - raise ValueError('base field must be finite') - n = getattr(other, '_order', None) - if n is not None: - self._order = n - def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, algorithm=None): r""" Return an elliptic-curve isogeny from this elliptic curve. diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 31e193a64b6..a8235585b17 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -1289,6 +1289,46 @@ def set_order(self, value, num_checks=8): self._order = value + def _fetch_cached_order(self, other): + r""" + This method copies the ``_order`` member from ``other`` to + ``self``. Both curves must have the same finite base field. + + This is used in + :class:`~sage.schemes.elliptic_curves.hom.EllipticCurveHom` + to keep track of an already computed curve order: According + to Tate's theorem [Tate1966b]_, isogenous elliptic curves + over a finite field have the same number of rational points. + + EXAMPLES:: + + sage: E1 = EllipticCurve(GF(2^127-1), [1,2,3,4,5]) + sage: E1.set_order(170141183460469231746191640949390434666) + sage: E2 = EllipticCurve(GF(2^127-1), [115649500210559831225094148253060920818, 36348294106991415644658737184600079491]) + sage: E2._fetch_cached_order(E1) + sage: E2._order + 170141183460469231746191640949390434666 + + TESTS:: + + sage: E3 = EllipticCurve(GF(17), [1,2,3,4,5]) + sage: hasattr(E3, '_order') + False + sage: E3._fetch_cached_order(E1) + Traceback (most recent call last): + ... + ValueError: curves have distinct base fields + """ + if hasattr(self, '_order') or not hasattr(other, '_order'): + return + F = self.base_field() + if F != other.base_field(): + raise ValueError('curves have distinct base fields') + n = getattr(other, '_order', None) + if n is not None: + self._order = n + + # dict to hold precomputed coefficient vectors of supersingular j values (excluding 0, 1728): supersingular_j_polynomials = {} diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py index bdf2c969079..e6806b7868e 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -35,6 +35,39 @@ class EllipticCurveHom(Morphism): """ Base class for elliptic-curve morphisms. """ + def __init__(self, *args, **kwds): + r""" + Constructor for elliptic-curve morphisms. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(257^2), [5,5]) + sage: P = E.lift_x(1) + sage: E.isogeny(P) # indirect doctest + Isogeny of degree 127 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2 to Elliptic Curve defined by y^2 = x^3 + 151*x + 22 over Finite Field in z2 of size 257^2 + sage: E.isogeny(P, algorithm='factored') # indirect doctest + Composite morphism of degree 127 = 127: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2 + To: Elliptic Curve defined by y^2 = x^3 + 151*x + 22 over Finite Field in z2 of size 257^2 + sage: E.isogeny(P, algorithm='velusqrt') # indirect doctest + Elliptic-curve isogeny (using √élu) of degree 127: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2 + To: Elliptic Curve defined by y^2 = x^3 + 119*x + 231 over Finite Field in z2 of size 257^2 + sage: E.montgomery_model(morphism=True) # indirect doctest + (Elliptic Curve defined by y^2 = x^3 + (199*z2+73)*x^2 + x over Finite Field in z2 of size 257^2, + Elliptic-curve morphism: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2 + To: Elliptic Curve defined by y^2 = x^3 + (199*z2+73)*x^2 + x over Finite Field in z2 of size 257^2 + Via: (u,r,s,t) = (88*z2 + 253, 208*z2 + 90, 0, 0)) + """ + super().__init__(*args, **kwds) + + # Over finite fields, isogenous curves have the same number of + # rational points, hence we copy over the cached curve orders. + if isinstance(self.base_ring(), finite_field_base.FiniteField) and self.degree(): + self._codomain._fetch_cached_order(self._domain) + self._domain._fetch_cached_order(self._codomain) + def _repr_type(self): """ Return a textual representation of what kind of morphism From 5b880a67eb036df92e435fdc12edc119bd090878 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Mon, 7 Nov 2022 23:00:29 +0800 Subject: [PATCH 358/414] copy point order through isomorphisms --- src/sage/schemes/elliptic_curves/weierstrass_morphism.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py index a9ad45f6fb2..77957adbd63 100644 --- a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py +++ b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py @@ -637,9 +637,11 @@ def __call__(self, P): """ if P[2] == 0: return self._codomain(0) - return self._codomain.point(baseWI.__call__(self, - tuple(P._coords)), - check=False) + res = baseWI.__call__(self, tuple(P._coords)) + Q = self._codomain.point(res, check=False) + if hasattr(P, '_order'): + Q._order = P._order + return Q def __invert__(self): r""" From 3c297c6e30171f071e7fb7187ac1a0587a0d803a Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Tue, 8 Nov 2022 14:20:50 +0800 Subject: [PATCH 359/414] copy point order through coprime-degree isogenies --- src/sage/schemes/elliptic_curves/ell_curve_isogeny.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index 141ef7ef1b1..c5f1f29919b 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -1181,7 +1181,16 @@ def _call_(self, P): yP = self.__posti_ratl_maps[1](xP, yP) xP = self.__posti_ratl_maps[0](xP) - return self._codomain(xP, yP) + Q = self._codomain(xP, yP) + if hasattr(P, '_order') and P._order.gcd(self._degree).is_one(): + # TODO: For non-coprime degree, the order of the point + # gets reduced by a divisor of the degree when passing + # through the isogeny. We could run something along the + # lines of order_from_multiple() to determine the new + # order, but this probably shouldn't happen by default + # as it'll be detrimental to performance in some cases. + Q._order = P._order + return Q def __getitem__(self, i): r""" From 177c0f6f2d3bb17a20c7c6e7661e9f94e0c8b872 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Tue, 8 Nov 2022 14:06:08 +0800 Subject: [PATCH 360/414] return original point when converting to same curve --- src/sage/schemes/elliptic_curves/ell_generic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 154a581bc2a..4cd98d31e00 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -559,6 +559,8 @@ def __call__(self, *args, **kwds): (ell_point.EllipticCurvePoint_field, ell_point.EllipticCurvePoint_number_field, ell_point.EllipticCurvePoint)): + if P.curve() is self: + return P # check if denominator of the point contains a factor of the # characteristic of the base ring. if so, coerce the point to # infinity. From ed3735769c4744334c3ae84513dc684cc97ed008 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Tue, 8 Nov 2022 14:33:31 +0800 Subject: [PATCH 361/414] add tests for copying of point orders --- .../schemes/elliptic_curves/ell_curve_isogeny.py | 10 ++++++++++ src/sage/schemes/elliptic_curves/hom_composite.py | 10 ++++++++++ .../elliptic_curves/weierstrass_morphism.py | 14 ++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index c5f1f29919b..1d8837356c1 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -1151,6 +1151,16 @@ def _call_(self, P): Traceback (most recent call last): ... TypeError: (20 : 90 : 1) fails to convert into the map's domain Elliptic Curve defined by y^2 = x^3 + 7*x over Number Field in th with defining polynomial x^2 + 3, but a `pushforward` method is not properly implemented + + Check that copying the order over works:: + + sage: E = EllipticCurve(GF(431), [1,0]) + sage: P, = E.gens() + sage: Q = 2^99*P; Q.order() + 27 + sage: phi = E.isogeny(3^99*P) + sage: phi(Q)._order + 27 """ if P.is_zero(): return self._codomain(0) diff --git a/src/sage/schemes/elliptic_curves/hom_composite.py b/src/sage/schemes/elliptic_curves/hom_composite.py index b2096eda5b8..7ca458dc43d 100644 --- a/src/sage/schemes/elliptic_curves/hom_composite.py +++ b/src/sage/schemes/elliptic_curves/hom_composite.py @@ -398,6 +398,16 @@ def _call_(self, P): sage: R = E.lift_x(15/4 * (a+3)) sage: psi(R) # indirect doctest (1033648757/303450 : 58397496786187/1083316500*a - 62088706165177/2166633000 : 1) + + Check that copying the order over works:: + + sage: E = EllipticCurve(GF(431), [1,0]) + sage: P, = E.gens() + sage: Q = 2^99*P; Q.order() + 27 + sage: phi = E.isogeny(3^99*P, algorithm='factored') + sage: phi(Q)._order + 27 """ return _eval_factored_isogeny(self._phis, P) diff --git a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py index 77957adbd63..85c673101d5 100644 --- a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py +++ b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py @@ -634,6 +634,20 @@ def __call__(self, P): (-3/4 : 3/4 : 1) sage: w(P).curve() == E.change_weierstrass_model((2,3,4,5)) True + + TESTS: + + Check that copying the order over works:: + + sage: E = EllipticCurve(GF(431^2), [1,0]) + sage: i = next(a for a in E.automorphisms() if a^2 == -a^24) + sage: P,_ = E.gens() + sage: P._order + 432 + sage: i(P)._order + 432 + sage: E(i(P))._order + 432 """ if P[2] == 0: return self._codomain(0) From 79629444afe7ecdaccae023876ead5ec5dcff415 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Tue, 8 Nov 2022 14:10:17 +0800 Subject: [PATCH 362/414] =?UTF-8?q?make=20use=20of=20new=20and=20improved?= =?UTF-8?q?=20caching=20in=20=E2=88=9A=C3=A9lu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schemes/elliptic_curves/hom_velusqrt.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index 144b5400970..8f0befbc5ca 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -896,24 +896,23 @@ def __init__(self, E, P, *, codomain=None, model=None, Q=None): if codomain is not None and model is not None: raise ValueError('cannot specify a codomain curve and model name simultaneously') - try: - self._raw_domain = E.short_weierstrass_model() - except ValueError: - raise NotImplementedError('only implemented for curves having a short Weierstrass model') - self._pre_iso = E.isomorphism_to(self._raw_domain) - try: P = E(P) except TypeError: raise ValueError('given kernel point P does not lie on E') - self._P = self._pre_iso(P) - - self._degree = self._P.order() + self._degree = P.order() if self._degree % 2 != 1 or self._degree < 9: raise NotImplementedError('only implemented for odd degrees >= 9') + try: + self._raw_domain = E.short_weierstrass_model() + except ValueError: + raise NotImplementedError('only implemented for curves having a short Weierstrass model') + self._pre_iso = E.isomorphism_to(self._raw_domain) + self._P = self._pre_iso(P) + if Q is not None: - self._Q = E(Q) + self._Q = self._pre_iso(E(Q)) EE = E else: self._Q = _point_outside_subgroup(self._P) # may extend base field From 8cb464d6bd4d314d33a0373ab62e7ea4c32391e9 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 8 Nov 2022 08:22:22 +0100 Subject: [PATCH 363/414] add missing dot --- src/sage/rings/invariants/invariant_theory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/invariants/invariant_theory.py b/src/sage/rings/invariants/invariant_theory.py index bb249b60560..0c0aa1c9f69 100644 --- a/src/sage/rings/invariants/invariant_theory.py +++ b/src/sage/rings/invariants/invariant_theory.py @@ -7,7 +7,7 @@ degree `d` in `n` variables. The special linear group `SL(n,\CC)` acts on the variables `(x_1,\dots, x_n)` linearly, -. MATH:: +.. MATH:: (x_1,\dots, x_n)^t \to A (x_1,\dots, x_n)^t ,\qquad From 2402ce0ea87028ef58a59e87a3855fa588316bb9 Mon Sep 17 00:00:00 2001 From: Lorenz Panny Date: Tue, 8 Nov 2022 17:36:17 +0800 Subject: [PATCH 364/414] fix random doctest failure --- src/sage/schemes/elliptic_curves/hom_velusqrt.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index 144b5400970..1a8809fc6aa 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -1390,7 +1390,7 @@ def _random_example_for_testing(): sage: 5 <= K.order() True """ - from sage.all import prime_range, choice, randrange, GF, gcd + from sage.all import prime_range, choice, randrange, GF, lcm, Mod while True: p = choice(prime_range(2, 100)) e = randrange(1,5) @@ -1412,9 +1412,10 @@ def _random_example_for_testing(): deg = choice(ds) break G = A.torsion_subgroup(deg) + os = G.generator_orders() while True: - v = [randrange(deg) for _ in range(G.ngens())] - if gcd([deg] + v) == 1: + v = [randrange(o) for o in os] + if lcm(Mod(c,o).additive_order() for c,o in zip(v,os)) == deg: break K = G(v).element() assert K.order() == deg From d6a834a1f1d3cd377b722bcbec97bfc1b468e252 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 8 Nov 2022 09:33:33 +0000 Subject: [PATCH 365/414] use an appropriate alogorithm to echelonize inexact matrices --- src/sage/matrix/matrix2.pyx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index ad0f7e58e15..6c437e42c17 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -63,6 +63,8 @@ AUTHORS: Pfaffian - Moritz Firsching(2020-10-05): added ``quantum_determinant`` + +- Dima Pasechnik (2022-11-08): fixed ``echelonize`` for inexact matrices """ # **************************************************************************** @@ -7599,6 +7601,15 @@ cdef class Matrix(Matrix1): sage: transformation_matrix = m.echelonize(transformation=True) sage: m == transformation_matrix * m_original True + + TESTS:: + + Check that :trac:`34724` is fixed (indirect doctest):: + + sage: a=RR(6.12323399573677e-17) + sage: m=matrix(RR,[[-a, -1.72508242466029], [ 0.579682446302195, a]]) + sage: (~m*m).norm() + 1.0 """ self.check_mutability() @@ -7617,7 +7628,11 @@ cdef class Matrix(Matrix1): except (AttributeError, TypeError): algorithm = 'scaled_partial_pivoting_valuation' else: - algorithm = 'classical' + try: + self.base_ring(1/2).abs() + algorithm = 'scaled_partial_pivoting' + except (AttributeError, ArithmeticError, TypeError): + algorithm = 'classical' try: if self.base_ring() in _Fields: if algorithm in ['classical', 'partial_pivoting', 'scaled_partial_pivoting', 'scaled_partial_pivoting_valuation']: From 3b1adb8d65dfe2c4e6d3091081a1b12b77209c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 8 Nov 2022 11:42:20 +0100 Subject: [PATCH 366/414] fix cython warning in blas_dict --- src/sage/data_structures/blas_dict.pxd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/data_structures/blas_dict.pxd b/src/sage/data_structures/blas_dict.pxd index f55b77975b4..7464c0daba8 100644 --- a/src/sage/data_structures/blas_dict.pxd +++ b/src/sage/data_structures/blas_dict.pxd @@ -7,6 +7,5 @@ cpdef dict sum(dict_iter) cpdef dict linear_combination(dict_factor_iter, bint factor_on_left=*) cpdef dict sum_of_monomials(monomials, scalar) cpdef dict sum_of_terms(index_coeff_pairs) -cdef inline dict remove_zeros(dict D) +cdef dict remove_zeros(dict D) cpdef dict convert_remove_zeroes(dict D, R) - From 3e917a48683f558c90819d93e0ed0cbecc6551aa Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Tue, 8 Nov 2022 21:01:19 +0900 Subject: [PATCH 367/414] Remove spurious changes --- src/doc/en/prep/Programming.rst | 2 +- .../en/prep/Symbolics-and-Basic-Plotting.rst | 2 +- src/sage/arith/misc.py | 8 ++++---- src/sage/combinat/sf/classical.py | 2 +- src/sage/finance/option.pyx | 2 +- src/sage/graphs/strongly_regular_db.pyx | 2 +- src/sage/interfaces/sage0.py | 2 +- src/sage/libs/singular/ring.pyx | 2 +- src/sage/misc/c3_controlled.pyx | 2 +- src/sage/misc/sagedoc_conf.py | 1 - src/sage/misc/sageinspect.py | 2 +- .../rings/finite_rings/hom_finite_field.pyx | 4 ++-- src/sage/rings/function_field/function_field.py | 2 +- src/sage/sets/disjoint_set.pyx | 8 ++++---- src/sage/structure/category_object.pyx | 6 +++--- src/sage/structure/factory.pyx | 2 +- src/sage/structure/list_clone.pyx | 4 ++-- src/sage/symbolic/expression.pyx | 1 + src/sage/symbolic/function.pyx | 17 +++++------------ src/sage/symbolic/getitem_impl.pxi | 2 +- src/sage/symbolic/ring.pyx | 7 ++----- src/sage_docbuild/conf.py | 6 +++--- src/sage_setup/cython_options.py | 5 ----- 23 files changed, 38 insertions(+), 53 deletions(-) diff --git a/src/doc/en/prep/Programming.rst b/src/doc/en/prep/Programming.rst index 056326927b6..3ae980667df 100644 --- a/src/doc/en/prep/Programming.rst +++ b/src/doc/en/prep/Programming.rst @@ -109,7 +109,7 @@ It is very important to keep in the parentheses. :: sage: A.det # Won't work - + This is so useful because we can use the 'tab' key, remember! diff --git a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst index bf11ef481b1..6e0ab69765a 100644 --- a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst +++ b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst @@ -178,7 +178,7 @@ This is a good place for a few reminders of basic help. :: sage: z.simplify - + Finally, recall that you can get nicely typeset versions of the output in several ways. diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 0eca5554dc9..1bd4d4c6a1f 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -2163,9 +2163,9 @@ def get_gcd(order): EXAMPLES:: sage: sage.arith.misc.get_gcd(4000) - + sage: sage.arith.misc.get_gcd(400000) - + sage: sage.arith.misc.get_gcd(4000000000) """ @@ -2185,9 +2185,9 @@ def get_inverse_mod(order): EXAMPLES:: sage: sage.arith.misc.get_inverse_mod(6000) - + sage: sage.arith.misc.get_inverse_mod(600000) - + sage: sage.arith.misc.get_inverse_mod(6000000000) """ diff --git a/src/sage/combinat/sf/classical.py b/src/sage/combinat/sf/classical.py index 3de5db72457..0477629f3d1 100644 --- a/src/sage/combinat/sf/classical.py +++ b/src/sage/combinat/sf/classical.py @@ -46,7 +46,7 @@ def init(): sage: sage.combinat.sf.classical.conversion_functions = {} sage: init() sage: sage.combinat.sf.classical.conversion_functions[('Schur', 'powersum')] - + The following checks if the bug described in :trac:`15312` is fixed. :: diff --git a/src/sage/finance/option.pyx b/src/sage/finance/option.pyx index 79c514c4677..31b290e0ce9 100644 --- a/src/sage/finance/option.pyx +++ b/src/sage/finance/option.pyx @@ -36,7 +36,7 @@ def black_scholes(double spot_price, double strike_price, double time_to_maturit sage: finance.black_scholes doctest:warning... DeprecationWarning: the package sage.finance is deprecated... - + sage: finance.black_scholes(42, 40, 0.5, 0.1, 0.2, 'call') # abs tol 1e-10 4.759422392871532 diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index 58ae72ae4e2..1e01738662d 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -1154,7 +1154,7 @@ def is_RSHCD(int v, int k, int l, int mu): sage: from sage.graphs.strongly_regular_db import is_RSHCD sage: t = is_RSHCD(64,27,10,12); t - [, 64, 27, 10, 12] + [, 64, 27, 10, 12] sage: g = t[0](*t[1:]); g Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) diff --git a/src/sage/interfaces/sage0.py b/src/sage/interfaces/sage0.py index 665a00f6525..a43a059e0ec 100644 --- a/src/sage/interfaces/sage0.py +++ b/src/sage/interfaces/sage0.py @@ -554,7 +554,7 @@ def _repr_(self): EXAMPLES:: sage: sage0(4).gcd - + """ return str(self._obj.parent().eval('%s.%s' % (self._obj._name, self._name))) diff --git a/src/sage/libs/singular/ring.pyx b/src/sage/libs/singular/ring.pyx index 828e782d887..04bd16e8784 100644 --- a/src/sage/libs/singular/ring.pyx +++ b/src/sage/libs/singular/ring.pyx @@ -772,7 +772,7 @@ cpdef poison_currRing(frame, event, arg): sage: from sage.libs.singular.ring import poison_currRing sage: sys.settrace(poison_currRing) sage: sys.gettrace() - + sage: sys.settrace(previous_trace_func) # switch it off again """ global currRing diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index 03561005657..ebadb070d1d 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -1091,7 +1091,7 @@ class HierarchyElement(object, metaclass=ClasscallMetaclass): sage: x._bases [5, 2] sage: x._key - + sage: x._key(10) 10 diff --git a/src/sage/misc/sagedoc_conf.py b/src/sage/misc/sagedoc_conf.py index 2d2a950c23e..6a29adb599d 100644 --- a/src/sage/misc/sagedoc_conf.py +++ b/src/sage/misc/sagedoc_conf.py @@ -23,7 +23,6 @@ def process_docstring_aliases(app, what, name, obj, options, docstringlines): """ Change the docstrings for aliases to point to the original object. """ - basename = name.rpartition('.')[2] if hasattr(obj, '__name__') and obj.__name__ != basename: docstringlines[:] = ['See :obj:`%s`.' % name] diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 3eb25382884..fbca2defc20 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -152,7 +152,7 @@ def is_function_or_cython_function(obj): sage: is_function_or_cython_function(_mul_parent) True sage: is_function_or_cython_function(Integer.digits) # unbound method - True + False sage: is_function_or_cython_function(Integer(1).digits) # bound method False diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index 75c91514aa2..81c7620d42d 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -435,7 +435,7 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): sage: Frob = k.frobenius_endomorphism() sage: embed = Frob.fixed_field()[1] sage: embed.__reduce__() # indirect doctest - (, + (, (, Set of field embeddings from Finite Field of size 5 to Finite Field in t of size 5^3, {}, @@ -835,7 +835,7 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): sage: k. = GF(5^3) sage: Frob = k.frobenius_endomorphism(2) sage: Frob.__reduce__() # indirect doctest - (, + (, (, Automorphism group of Finite Field in t of size 5^3, {}, diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index f1e130e3b0e..e8cf51beda6 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -128,7 +128,7 @@ sage: TestSuite(S).run() # long time (4s) Global function fields -====================== +---------------------- A global function field in Sage is an extension field of a rational function field over a *finite* constant field by an irreducible separable polynomial over the diff --git a/src/sage/sets/disjoint_set.pyx b/src/sage/sets/disjoint_set.pyx index dac6e75779b..aaa12438f15 100644 --- a/src/sage/sets/disjoint_set.pyx +++ b/src/sage/sets/disjoint_set.pyx @@ -358,14 +358,14 @@ cdef class DisjointSet_of_integers(DisjointSet_class): sage: d = DisjointSet(5) sage: d.__reduce__() - (, (5,), [0, 1, 2, 3, 4]) + (, (5,), [0, 1, 2, 3, 4]) :: sage: d.union(2,4) sage: d.union(1,3) sage: d.__reduce__() - (, (5,), [0, 1, 2, 1, 2]) + (, (5,), [0, 1, 2, 1, 2]) """ return DisjointSet, (self._nodes.degree,), self.__getstate__() @@ -674,7 +674,7 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): {{0}, {1}, {2}, {3}, {4}} sage: d = _ sage: d.__reduce__() - (, + (, ([0, 1, 2, 3, 4],), [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]) @@ -683,7 +683,7 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): sage: d.union(2,4) sage: d.union(1,3) sage: d.__reduce__() - (, + (, ([0, 1, 2, 3, 4],), [(0, 0), (1, 1), (2, 2), (3, 1), (4, 2)]) """ diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index 99679d2e3ca..52abd7d918b 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -571,7 +571,7 @@ cdef class CategoryObject(SageObject): sage: F.base_ring() Integer Ring sage: F.__class__.base_ring - + Note that the coordinates of the elements of a module can lie in a bigger ring, the ``coordinate_ring``:: @@ -591,7 +591,7 @@ cdef class CategoryObject(SageObject): sage: F.base_ring() Rational Field sage: F.__class__.base_ring - + sage: E = CombinatorialFreeModule(ZZ, [1,2,3]) sage: F = CombinatorialFreeModule(ZZ, [2,3,4]) @@ -599,7 +599,7 @@ cdef class CategoryObject(SageObject): sage: H.base_ring() Integer Ring sage: H.__class__.base_ring - + .. TODO:: diff --git a/src/sage/structure/factory.pyx b/src/sage/structure/factory.pyx index 4e656be479f..a4a13186973 100644 --- a/src/sage/structure/factory.pyx +++ b/src/sage/structure/factory.pyx @@ -550,7 +550,7 @@ cdef class UniqueFactory(SageObject): sage: a = test_factory(1, 2) Making object (1, 2) sage: test_factory.reduce_data(a) - (, + (, (, (...), (1, 2), diff --git a/src/sage/structure/list_clone.pyx b/src/sage/structure/list_clone.pyx index 50e57b34ca9..125f32762ef 100644 --- a/src/sage/structure/list_clone.pyx +++ b/src/sage/structure/list_clone.pyx @@ -934,7 +934,7 @@ cdef class ClonableArray(ClonableElement): sage: loads(dumps(el)) [1, 2, 4] sage: t = el.__reduce__(); t - (, + (, (, , [1, 2, 4], @@ -1720,7 +1720,7 @@ cdef class ClonableIntArray(ClonableElement): sage: loads(dumps(el)) [1, 2, 4] sage: t = el.__reduce__(); t - (, + (, (, , [1, 2, 4], diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 2c9c1ba894c..903a8541460 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -6363,6 +6363,7 @@ cdef class Expression(Expression_abc): sage: type(u._unpack_operands()[0]) <... 'tuple'> """ + from sage.symbolic.expression import unpack_operands return unpack_operands(self) def operands(self): diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index 24cb7adc6c1..0baedce69c0 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -141,6 +141,11 @@ is attempted, and after that ``sin()`` which succeeds:: from sage.structure.sage_object cimport SageObject from sage.structure.element cimport Element, parent, Expression from sage.misc.lazy_attribute import lazy_attribute +from .expression import ( + call_registered_function, find_registered_function, register_or_update_function, + get_sfunction_from_hash +) +from .expression import get_sfunction_from_serial as get_sfunction_from_serial from sage.structure.coerce cimport (coercion_model, py_scalar_to_element, is_numpy_type, is_mpmath_type) @@ -258,8 +263,6 @@ cdef class Function(SageObject): f(x) """ - from .expression import register_or_update_function - self._serial = register_or_update_function(self, self._name, self._latex_name, self._nargs, self._evalf_params_first, False) @@ -541,8 +544,6 @@ cdef class Function(SageObject): if not isinstance(a, Expression): raise TypeError("arguments must be symbolic expressions") - from .expression import call_registered_function - return call_registered_function(self._serial, self._nargs, args, hold, not symbolic_input, SR) @@ -837,8 +838,6 @@ cdef class GinacFunction(BuiltinFunction): preserved_arg=preserved_arg, alt_name=alt_name) cdef _is_registered(self): - from .expression import find_registered_function, get_sfunction_from_serial - # Since this is function is defined in C++, it is already in # ginac's function registry fname = self._ginac_name if self._ginac_name is not None else self._name @@ -846,8 +845,6 @@ cdef class GinacFunction(BuiltinFunction): return bool(get_sfunction_from_serial(self._serial)) cdef _register_function(self): - from .expression import register_or_update_function - # We don't need to add anything to GiNaC's function registry # However, if any custom methods were provided in the python class, # we should set the properties of the function_options object @@ -1097,8 +1094,6 @@ cdef class BuiltinFunction(Function): sage: loads(dumps(cot)) == cot # trac #15138 True """ - from .expression import find_registered_function, get_sfunction_from_serial - # check if already defined cdef unsigned int serial @@ -1196,8 +1191,6 @@ cdef class SymbolicFunction(Function): evalf_params_first) cdef _is_registered(SymbolicFunction self): - from .expression import get_sfunction_from_hash - # see if there is already a SymbolicFunction with the same state cdef long myhash = self._hash_() cdef SymbolicFunction sfunc = get_sfunction_from_hash(myhash) diff --git a/src/sage/symbolic/getitem_impl.pxi b/src/sage/symbolic/getitem_impl.pxi index ff8a9d94138..72bf9d5f0e5 100644 --- a/src/sage/symbolic/getitem_impl.pxi +++ b/src/sage/symbolic/getitem_impl.pxi @@ -182,7 +182,7 @@ cdef class OperandsWrapper(SageObject): TESTS:: sage: (x^2).op.__reduce__() - (, (x^2,)) + (, (x^2,)) sage: loads(dumps((x^2).op)) Operands of x^2 """ diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 77002bef25c..2036a7331d4 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -52,11 +52,8 @@ from sage.structure.coerce cimport is_numpy_type import sage.rings.abc from sage.rings.integer_ring import ZZ -# is_SymbolicVariable used to be defined here; re-export it here lazily -cpdef bint is_SymbolicVariable(x): - from sage.symbolic.expression import _is_SymbolicVariable - - return _is_SymbolicVariable(x) +# is_SymbolicVariable used to be defined here; re-export it +from sage.symbolic.expression import _is_SymbolicVariable as is_SymbolicVariable import keyword import operator diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index a369978cf1d..4c880cdca63 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -40,12 +40,12 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ + 'sage_docbuild.ext.inventory_builder', + 'sage_docbuild.ext.multidocs', + 'sage_docbuild.ext.sage_autodoc', 'sphinx.ext.todo', 'sphinx.ext.extlinks', 'sphinx.ext.mathjax', - 'sage_docbuild.ext.sage_autodoc', - 'sage_docbuild.ext.inventory_builder', - 'sage_docbuild.ext.multidocs', 'IPython.sphinxext.ipython_directive', 'matplotlib.sphinxext.plot_directive', 'jupyter_sphinx', diff --git a/src/sage_setup/cython_options.py b/src/sage_setup/cython_options.py index a878fb754e4..086aa070ca9 100644 --- a/src/sage_setup/cython_options.py +++ b/src/sage_setup/cython_options.py @@ -10,11 +10,6 @@ def compiler_directives(profile: bool): auto_pickle=False, # Do not create __test__ dictionary automatically from docstrings autotestdict=False, - # When enabled, functions will bind to an instance when looked up as a - # class attribute (hence the name) and will emulate the attributes of - # Python functions, including introspections like argument names and - # annotations - binding=True, # Do not check for division by 0 (this is about 35% quicker than with check) cdivision=True, # Embed a textual copy of the call signature in the docstring (to support tools like IPython) From 24299ced2b38daef48a2002d24c1766c80d2d0e9 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 8 Nov 2022 16:26:22 +0000 Subject: [PATCH 368/414] work around SR being inexact, fix a silly doctest --- src/sage/matrix/matrix2.pyx | 45 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 6c437e42c17..f33254c5c1b 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -90,6 +90,7 @@ from sage.categories.all import Fields, IntegralDomains from sage.rings.ring import is_Ring from sage.rings.number_field.number_field_base import is_NumberField from sage.rings.integer_ring import ZZ, is_IntegerRing +from sage.symbolic.ring import SR from sage.rings.integer import Integer from sage.rings.rational_field import QQ, is_RationalField import sage.rings.abc @@ -799,7 +800,7 @@ cdef class Matrix(Matrix1): sage: RF = RealField(52) sage: B = matrix(RF, 2, 2, 1) sage: A = matrix(RF, [[0.24, 1, 0], [1, 0, 0]]) - sage: 0 < (A * A.solve_right(B) - B).norm() < 1e-14 + sage: 0 <= (A * A.solve_right(B) - B).norm() < 1e-14 True Over the inexact ring ``SR``, we can still verify the solution @@ -1536,19 +1537,15 @@ cdef class Matrix(Matrix1): Beware that the ``exact`` algorithm is not numerically stable, but the default ``numpy`` algorithm is:: - sage: M = matrix(RR, 3, 3, [1,2,3,1/3,2/3,3/3,1/5,2/5,3/5]) - sage: M.pseudoinverse() # tol 1e-15 - [0.0620518477661335 0.0206839492553778 0.0124103695532267] - [ 0.124103695532267 0.0413678985107557 0.0248207391064534] - [ 0.186155543298400 0.0620518477661335 0.0372311086596801] - sage: M.pseudoinverse(algorithm="numpy") # tol 1e-15 - [0.0620518477661335 0.0206839492553778 0.0124103695532267] - [ 0.124103695532267 0.0413678985107557 0.0248207391064534] - [ 0.186155543298400 0.0620518477661335 0.0372311086596801] - sage: M.pseudoinverse(algorithm="exact") - [ 0.125000000000000 0.0625000000000000 0.0312500000000000] - [ 0.250000000000000 0.125000000000000 0.0625000000000000] - [ 0.000000000000000 0.000000000000000 0.0625000000000000] + sage: M = matrix.hilbert(12,ring=RR) + sage: (~M*M).norm() # a considerable error + 1.3... + sage: Mx = M.pseudoinverse(algorithm="exact") + sage: (Mx*M).norm() # huge error + 11.5... + sage: Mx = M.pseudoinverse(algorithm="numpy") + sage: (Mx*M).norm() # still OK + 1.00... When multiplying the given matrix with the pseudoinverse, the result is symmetric for the ``exact`` algorithm or hermitian @@ -1582,6 +1579,11 @@ cdef class Matrix(Matrix1): [-1286742750677287/643371375338643 1000799917193445/1000799917193444] [ 519646110850445/346430740566963 -300239975158034/600479950316067] + Although it is not too far off:: + + sage: (~M-M.pseudoinverse(algorithm="numpy")).norm() < 1e-14 + True + TESTS:: sage: M.pseudoinverse(algorithm="exact") @@ -7606,12 +7608,13 @@ cdef class Matrix(Matrix1): Check that :trac:`34724` is fixed (indirect doctest):: - sage: a=RR(6.12323399573677e-17) + sage: a=6.12323399573677e-17 sage: m=matrix(RR,[[-a, -1.72508242466029], [ 0.579682446302195, a]]) sage: (~m*m).norm() 1.0 """ self.check_mutability() + basring = self.base_ring() if algorithm == 'default': from sage.categories.discrete_valuation import DiscreteValuationFields @@ -7621,20 +7624,22 @@ cdef class Matrix(Matrix1): # In general, we would like to do so in any rank one valuation ring, # but this should be done by introducing a category of general valuation rings and fields, # which we don't have at the moment - elif self.base_ring() in DiscreteValuationFields(): + elif basring in DiscreteValuationFields(): try: - self.base_ring().one().abs() + basring.one().abs() algorithm = 'scaled_partial_pivoting' except (AttributeError, TypeError): algorithm = 'scaled_partial_pivoting_valuation' + elif (basring is SR) or basring.is_exact(): + algorithm = 'classical' else: try: - self.base_ring(1/2).abs() + (basring(0.42)).abs() algorithm = 'scaled_partial_pivoting' except (AttributeError, ArithmeticError, TypeError): algorithm = 'classical' try: - if self.base_ring() in _Fields: + if basring in _Fields: if algorithm in ['classical', 'partial_pivoting', 'scaled_partial_pivoting', 'scaled_partial_pivoting_valuation']: self._echelon_in_place(algorithm) elif algorithm == 'strassen': @@ -7646,7 +7651,7 @@ cdef class Matrix(Matrix1): kwds['algorithm'] = algorithm return self._echelonize_ring(**kwds) except ArithmeticError as msg: - raise NotImplementedError("%s\nEchelon form not implemented over '%s'."%(msg,self.base_ring())) + raise NotImplementedError("%s\nEchelon form not implemented over '%s'."%(msg,basring)) def echelon_form(self, algorithm="default", cutoff=0, **kwds): r""" From cce62c225276687c4f9e331688bd638085e2dbcd Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 8 Nov 2022 17:41:51 +0000 Subject: [PATCH 369/414] use subclassed Matrix_symbolic_dense --- src/sage/matrix/matrix2.pyx | 3 +-- src/sage/matrix/matrix_symbolic_dense.pyx | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index f33254c5c1b..4e4cdff7efe 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -90,7 +90,6 @@ from sage.categories.all import Fields, IntegralDomains from sage.rings.ring import is_Ring from sage.rings.number_field.number_field_base import is_NumberField from sage.rings.integer_ring import ZZ, is_IntegerRing -from sage.symbolic.ring import SR from sage.rings.integer import Integer from sage.rings.rational_field import QQ, is_RationalField import sage.rings.abc @@ -7630,7 +7629,7 @@ cdef class Matrix(Matrix1): algorithm = 'scaled_partial_pivoting' except (AttributeError, TypeError): algorithm = 'scaled_partial_pivoting_valuation' - elif (basring is SR) or basring.is_exact(): + elif basring.is_exact(): algorithm = 'classical' else: try: diff --git a/src/sage/matrix/matrix_symbolic_dense.pyx b/src/sage/matrix/matrix_symbolic_dense.pyx index 9409ff5c1e8..2eca331a6a2 100644 --- a/src/sage/matrix/matrix_symbolic_dense.pyx +++ b/src/sage/matrix/matrix_symbolic_dense.pyx @@ -166,6 +166,9 @@ cdef maxima from sage.calculus.calculus import symbolic_expression_from_maxima_string, maxima cdef class Matrix_symbolic_dense(Matrix_generic_dense): + def echelonize(self, algorithm="classical", cutoff=0, **kwds): + return super().echelonize(algorithm="classical", cutoff=cutoff, **kwds) + def eigenvalues(self, extend=True): """ Compute the eigenvalues by solving the characteristic From 75026c0d2ab259a01854695c9fdf115c90c5dd8d Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Tue, 8 Nov 2022 12:15:48 -0700 Subject: [PATCH 370/414] trac 34733 noncommutative quo_rem --- src/sage/rings/polynomial/polynomial_element.pyx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index cea15d34764..243bbff5553 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -11662,6 +11662,17 @@ cdef class Polynomial_generic_dense(Polynomial): ... ZeroDivisionError: division by zero polynomial + Polynomials over noncommutative rings are also allowed + (after :trac:`34733`):: + + sage: HH = QuaternionAlgebra(QQ, -1, -1) + sage: P. = HH[] + sage: f = P.random_element(5) + sage: g = P.random_element((0, 5)) + sage: q,r = f.quo_rem(g) + sage: f == q*g + r + True + TESTS: The following shows that :trac:`16649` is indeed fixed. :: @@ -11709,7 +11720,7 @@ cdef class Polynomial_generic_dense(Polynomial): convert = True if convert: for k from m-n >= k >= 0: - q = inv * x[n+k-1] + q = x[n+k-1] * inv try: q = R(q) except TypeError: @@ -11719,7 +11730,7 @@ cdef class Polynomial_generic_dense(Polynomial): quo.append(q) else: for k from m-n >= k >= 0: - q = inv * x[n+k-1] + q = x[n+k-1] * inv for j from n+k-2 >= j >= k: x[j] -= q * y[j-k] quo.append(q) From aa74d4badcbe6841833ff983ad5f756c5ac78752 Mon Sep 17 00:00:00 2001 From: Kwankyu Lee Date: Wed, 9 Nov 2022 12:17:56 +0900 Subject: [PATCH 371/414] More edits --- src/sage_docbuild/ext/sage_autodoc.py | 57 ++++++++++++++------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 418cf67dbd6..b36ed2ee25e 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -3,7 +3,7 @@ This is :mod:`sphinx.ext.autodoc` extension modified for Sage objects. -The original headline of :mod:`sphinx.ext.autodoc`: +The original header of :mod:`sphinx.ext.autodoc`: Extension to create automatic documentation from code docstrings. @@ -11,11 +11,13 @@ the doctree, thus avoiding duplication between docstrings and documentation for those who like elaborate docstrings. -Presently this module is based on :mod:`sphinx.ext.autodoc` from Sphinx version 5.3.0. -The upstream original source file is `sphinx/ext/autodoc/__init__.py `_. +This module is currently based on :mod:`sphinx.ext.autodoc` from Sphinx version +5.3.0. Compare against the upstream original source file +`sphinx/ext/autodoc/__init__.py +`_. In the source file of this module, major modifications are delimited by a pair -of comment dividers. To lessen maintenance burdens, we aim at reducing those modifications. +of comment dividers. To lessen maintenance burden, we aim at reducing the modifications. AUTHORS: @@ -510,8 +512,6 @@ def format_signature(self, **kwargs: Any) -> str: Let the user process it via the ``autodoc-process-signature`` event. """ - #if 'Expression.numerical_approx' in self.name: - # from celery.contrib import rdb; rdb.set_trace() if self.args is not None: # signature given explicitly args = "(%s)" % self.args @@ -1338,10 +1338,10 @@ def format_args(self, **kwargs: Any) -> str: try: self.env.app.emit('autodoc-before-process-signature', self.object, False) - # -------------------------------------------- + # ---------------------------------------------------------------- # Trac #9976: Support the _sage_argspec_ attribute which makes it # possible to get argument specification of decorated callables in - # documentation correct. See e.g. sage.misc.decorators.sage_wraps + # documentation correct. See e.g. sage.misc.decorators.sage_wraps obj = self.object if hasattr(obj, "_sage_argspec_"): @@ -1362,7 +1362,7 @@ def format_args(self, **kwargs: Any) -> str: if argspec is None: return None args = sage_formatargspec(*argspec) - # -------------------------------------------- + # ---------------------------------------------------------------- except TypeError as exc: logger.warning(__("Failed to get a function signature for %s: %s"), self.fullname, exc) @@ -1542,15 +1542,10 @@ def import_object(self, raiseerror: bool = False) -> bool: if ret: if hasattr(self.object, '__name__'): self.doc_as_attr = (self.objpath[-1] != self.object.__name__) - # ------------------------------------------------------------------ - # Trac #27692: - # - # For Python 3, we make use of self.object.__qualname__. - # - # Notes from trac #7448: - # - # The original goal of this was that if some class is aliased, the - # alias is generated as a link rather than duplicated. For example in + # ------------------------------------------------------------------- + # Trac #27692, #7448: The original goal of this was that if some + # class is aliased, the alias is generated as a link rather than + # duplicated. For example in # # class A: # pass @@ -1608,7 +1603,7 @@ def import_object(self, raiseerror: bool = False) -> bool: cls = getattr(cls, part, None) self.doc_as_attr = (self.objpath != qualname_parts and self.object is cls) - # ------------------------------------------------------------------ + # ------------------------------------------------------------------- else: self.doc_as_attr = True return ret @@ -2291,13 +2286,13 @@ def format_args(self, **kwargs: Any) -> str: kwargs.setdefault('unqualified_typehints', True) # ----------------------------------------------------------------- - # Trac #9976: This function has been rewritten to support the - # _sage_argspec_ attribute which makes it possible to get argument - # specification of decorated callables in documentation correct. - # See e.g. sage.misc.decorators.sage_wraps. + # Trac #9976: Support the _sage_argspec_ attribute which makes it + # possible to get argument specification of decorated callables in + # documentation correct. See e.g. sage.misc.decorators.sage_wraps. # # Note, however, that sage.misc.sageinspect.sage_getargspec already - # uses a method _sage_argspec_, that only works on objects, not on classes, though. + # uses a method _sage_argspec_, that only works on objects, not on + # classes, though. obj = self.object if hasattr(obj, "_sage_argspec_"): argspec = obj._sage_argspec_() @@ -2341,6 +2336,11 @@ def add_directive_header(self, sig: str) -> None: def document_members(self, all_members: bool = False) -> None: pass + # ------------------------------------------------------------------------ + # Trac #34730: The format_signature() of the class MethodDocumenter + # supports overloaded methods via inspect.signature(), which does not work + # with Sage yet. Hence the method was removed from here. + # ------------------------------------------------------------------------ def merge_default_value(self, actual: Signature, overload: Signature) -> Signature: """Merge default values of actual implementation to the overload variants.""" @@ -2665,8 +2665,9 @@ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: ) -> bool: if isinstance(parent, ModuleDocumenter): return False - # ------------------------------------------------------------------- - # Trac: Do not pass objects of class CachedMethodCallerNoArgs as attributes. + # --------------------------------------------------------------------- + # Trac #34730: Do not pass objects of the class CachedMethodCaller as + # attributes. # # sage: from sphinx.util import inspect # sage: A = AlgebrasWithBasis(QQ).example() @@ -2678,14 +2679,14 @@ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: # sage: inspect.isroutine(member) # True # - from sage.misc.classcall_metaclass import ClasscallMetaclass if inspect.isattributedescriptor(member) and not inspect.isroutine(member): return True # Trac #26522: Pass objects of classes that inherit ClasscallMetaclass # as attributes rather than method descriptors. + from sage.misc.classcall_metaclass import ClasscallMetaclass if isinstance(type(member), ClasscallMetaclass): return True - # ------------------------------------------------------------------- + # --------------------------------------------------------------------- elif not inspect.isroutine(member) and not isinstance(member, type): return True else: From af97acc4a3e7f5087e56235887a760147636c5bc Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Tue, 8 Nov 2022 22:53:33 -0700 Subject: [PATCH 372/414] add another doctest --- .../rings/polynomial/polynomial_element_generic.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index 868a5f691f9..64fcf65b210 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -824,6 +824,16 @@ def quo_rem(self, other): sage: f.quo_rem(g) (-y^5 + 2*y^2, y^3 - 2*x^2*y^2 - y) + Polynomials over noncommutative rings are also allowed:: + + sage: HH = QuaternionAlgebra(QQ, -1, -1) + sage: P. = PolynomialRing(HH, sparse=True) + sage: f = P.random_element(5) + sage: g = P.random_element((0, 5)) + sage: q, r = f.quo_rem(g) + sage: f == q*g + r + True + TESTS:: sage: P. = PolynomialRing(ZZ, sparse=True) From 51dfa8676979bd4e3e373fdbdf9df023a84ca16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 9 Nov 2022 13:58:35 +0100 Subject: [PATCH 373/414] some details in hyperelliptic_padic_field --- .../hyperelliptic_padic_field.py | 61 ++++++++----------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py index 373394b20af..a79843c0e99 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py @@ -1,15 +1,15 @@ """ Hyperelliptic curves over a `p`-adic field """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Robert Bradshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.rings.all import (PowerSeriesRing, PolynomialRing, ZZ, QQ, @@ -27,10 +27,10 @@ class HyperellipticCurve_padic_field(hyperelliptic_generic.HyperellipticCurve_generic, ProjectivePlaneCurve_field): -# The functions below were prototyped at the 2007 Arizona Winter School by -# Robert Bradshaw and Ralf Gerkmann, working with Miljan Brakovevic and -# Kiran Kedlaya -# All of the below is with respect to the Monsky Washnitzer cohomology. + # The functions below were prototyped at the 2007 Arizona Winter School by + # Robert Bradshaw and Ralf Gerkmann, working with Miljan Brakovevic and + # Kiran Kedlaya + # All of the below is with respect to the Monsky Washnitzer cohomology. def local_analytic_interpolation(self, P, Q): """ @@ -158,7 +158,7 @@ def weierstrass_points(self): raise NotImplementedError() return [self((0,1,0))] + [self((x, 0, 1)) for x in f.roots(multiplicities=False)] - def is_in_weierstrass_disc(self,P): + def is_in_weierstrass_disc(self, P): """ Checks if `P` is in a Weierstrass disc @@ -186,12 +186,9 @@ def is_in_weierstrass_disc(self,P): - Jennifer Balakrishnan (2010-02) """ - if (P[1].valuation() == 0 and P != self(0,1,0)): - return False - else: - return True + return not (P[1].valuation() == 0 and P != self(0, 1, 0)) - def is_weierstrass(self,P): + def is_weierstrass(self, P): """ Checks if `P` is a Weierstrass point (i.e., fixed by the hyperelliptic involution) @@ -218,12 +215,8 @@ def is_weierstrass(self,P): AUTHOR: - Jennifer Balakrishnan (2010-02) - """ - if (P[1] == 0 or P[2] ==0): - return True - else: - return False + return (P[1] == 0 or P[2] == 0) def find_char_zero_weier_point(self, Q): """ @@ -260,7 +253,7 @@ def find_char_zero_weier_point(self, Q): if self.is_same_disc(P,Q): return P - def residue_disc(self,P): + def residue_disc(self, P): """ Gives the residue disc of `P` @@ -306,7 +299,7 @@ def residue_disc(self,P): else: return HF(0,1,0) - def is_same_disc(self,P,Q): + def is_same_disc(self, P, Q): """ Checks if `P,Q` are in same residue disc @@ -326,10 +319,7 @@ def is_same_disc(self,P,Q): sage: HK.is_same_disc(Q,S) False """ - if self.residue_disc(P) == self.residue_disc(Q): - return True - else: - return False + return self.residue_disc(P) == self.residue_disc(Q) def tiny_integrals(self, F, P, Q): r""" @@ -1001,12 +991,12 @@ def newton_sqrt(self, f, x0, prec): - Jennifer Balakrishnan """ z = x0 - loop_prec = (log(RR(prec))/log(RR(2))).ceil() + loop_prec = log(RR(prec), 2).ceil() for i in range(loop_prec): - z = (z + f/z) / 2 + z = (z + f / z) / 2 return z - def curve_over_ram_extn(self,deg): + def curve_over_ram_extn(self, deg): r""" Return ``self`` over `\QQ_p(p^(1/deg))`. @@ -1123,7 +1113,7 @@ def P_to_S(self, P, S): val = [I(S[1]) for I in integrals] return vector(val) - def coleman_integral_P_to_S(self,w,P,S): + def coleman_integral_P_to_S(self, w, P, S): r""" Given a finite Weierstrass point `P` and a point `S` in the same disc, computes the Coleman integral `\int_P^S w` @@ -1168,7 +1158,7 @@ def coleman_integral_P_to_S(self,w,P,S): int_sing_a = int_sing(S[1]) return int_sing_a - def S_to_Q(self,S,Q): + def S_to_Q(self, S, Q): r""" Given `S` a point on self over an extension field, computes the Coleman integrals `\{\int_S^Q x^i dx/2y \}_{i=0}^{2g-1}` @@ -1248,13 +1238,12 @@ def S_to_Q(self,S,Q): b = V(L) M_sys = matrix(K, M_frob).transpose() - 1 B = (~M_sys) - v = [c.valuation() for c in B.list()] - vv = min(v) + vv = min(c.valuation() for c in B.list()) B = (p**(-vv)*B).change_ring(K) B = p**(vv)*B return B*(b-S_to_FS-FQ_to_Q) - def coleman_integral_S_to_Q(self,w,S,Q): + def coleman_integral_S_to_Q(self, w, S, Q): r""" Compute the Coleman integral `\int_S^Q w` @@ -1293,7 +1282,6 @@ def coleman_integral_S_to_Q(self,w,S,Q): AUTHOR: - Jennifer Balakrishnan - """ import sage.schemes.hyperelliptic_curves.monsky_washnitzer as monsky_washnitzer K = self.base_ring() @@ -1350,10 +1338,9 @@ def coleman_integral_from_weierstrass_via_boundary(self, w, P, Q, d): AUTHOR: - Jennifer Balakrishnan - """ HJ = self.curve_over_ram_extn(d) - S = self.get_boundary_point(HJ,P) - P_to_S = self.coleman_integral_P_to_S(w,P,S) - S_to_Q = HJ.coleman_integral_S_to_Q(w,S,Q) + S = self.get_boundary_point(HJ, P) + P_to_S = self.coleman_integral_P_to_S(w, P, S) + S_to_Q = HJ.coleman_integral_S_to_Q(w, S, Q) return P_to_S + S_to_Q From 0c7da0a8b7a7199c6a1e3247cf8d476e5bdb3478 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Wed, 9 Nov 2022 13:36:13 +0000 Subject: [PATCH 374/414] hardcode "classical" algorithm, add a doctest --- src/sage/matrix/matrix_symbolic_dense.pyx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/sage/matrix/matrix_symbolic_dense.pyx b/src/sage/matrix/matrix_symbolic_dense.pyx index 2eca331a6a2..8774848f2d7 100644 --- a/src/sage/matrix/matrix_symbolic_dense.pyx +++ b/src/sage/matrix/matrix_symbolic_dense.pyx @@ -166,8 +166,20 @@ cdef maxima from sage.calculus.calculus import symbolic_expression_from_maxima_string, maxima cdef class Matrix_symbolic_dense(Matrix_generic_dense): - def echelonize(self, algorithm="classical", cutoff=0, **kwds): - return super().echelonize(algorithm="classical", cutoff=cutoff, **kwds) + def echelonize(self, **kwds): + """ + Echelonize using the classical algorithm. + + + TESTS:: + + sage: m = matrix([[cos(pi/5), sin(pi/5)], [-sin(pi/5), cos(pi/5)]]) + sage: m.echelonize(); m + [1 0] + [0 1] + """ + + return super().echelonize(algorithm="classical", **kwds) def eigenvalues(self, extend=True): """ From c0eaf1e9a7ad01bbf8c6a464afbfee438cc0c62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 9 Nov 2022 14:56:47 +0100 Subject: [PATCH 375/414] cleanup for QQ-curves --- src/sage/schemes/elliptic_curves/Qcurves.py | 47 +++++++++++++-------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/sage/schemes/elliptic_curves/Qcurves.py b/src/sage/schemes/elliptic_curves/Qcurves.py index bf6b7b0d36d..4c9e8f3718a 100644 --- a/src/sage/schemes/elliptic_curves/Qcurves.py +++ b/src/sage/schemes/elliptic_curves/Qcurves.py @@ -9,7 +9,6 @@ The code here implements the algorithm of Cremona and Najman presented in [CrNa2020]_. """ - ############################################################################## # Copyright (C) 2020-2021 John Cremona # @@ -24,10 +23,10 @@ # # https://www.gnu.org/licenses/ ############################################################################## - from sage.rings.rational_field import QQ from sage.rings.polynomial.polynomial_ring import polygen + def is_Q_curve(E, maxp=100, certificate=False, verbose=False): r""" Return whether ``E`` is a `\QQ`-curve, with optional certificate. @@ -195,16 +194,25 @@ def is_Q_curve(E, maxp=100, certificate=False, verbose=False): 'core_poly': x^2 - 840064*x + 1593413632, 'r': 1, 'rho': 1} + + TESTS:: + + sage: E = EllipticCurve([GF(5)(t) for t in [2,3,5,7,11]]) + sage: is_Q_curve(E) + Traceback (most recent call last): + ... + TypeError: Elliptic Curve defined by ... must be an elliptic curve + defined over a number field """ from sage.rings.number_field.number_field_base import is_NumberField if verbose: - print("Checking whether {} is a Q-curve".format(E)) + print(f"Checking whether {E} is a Q-curve") try: assert is_NumberField(E.base_field()) except (AttributeError, AssertionError): - raise TypeError("{} must be an elliptic curve defined over a number field in is_Q_curve()") + raise TypeError(f"{E} must be an elliptic curve defined over a number field") from sage.rings.integer_ring import ZZ from sage.arith.functions import lcm @@ -224,7 +232,7 @@ def is_Q_curve(E, maxp=100, certificate=False, verbose=False): # test for CM for d, f, j in cm_j_invariants_and_orders(QQ): if jE == j: - return True, {'CM': d*f**2} + return True, {'CM': d * f**2} # else not CM return True, {'CM': ZZ(0), 'r': ZZ(0), 'rho': ZZ(0), 'N': ZZ(1), 'core_poly': polygen(QQ)} else: @@ -234,7 +242,7 @@ def is_Q_curve(E, maxp=100, certificate=False, verbose=False): flag, df = is_cm_j_invariant(jE) if flag: d, f = df - D = d*f**2 + D = d * f**2 if verbose: print("Yes: E is CM (discriminant {})".format(D)) if certificate: @@ -246,15 +254,15 @@ def is_Q_curve(E, maxp=100, certificate=False, verbose=False): K = E.base_field() jpoly = jE.minpoly() - if jpoly.degree() Date: Wed, 9 Nov 2022 15:14:21 +0100 Subject: [PATCH 376/414] use pari for roots of unity --- src/sage/rings/number_field/number_field.py | 40 ++++++++++----------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index a2f0e3d047c..4febd05f45d 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -7388,13 +7388,13 @@ def zeta(self, n=2, all=False): sage: K.zeta(2, all=True) [-1] sage: K.zeta(3) - 1/2*z - 1/2 + -1/2*z - 1/2 sage: K.zeta(3, all=True) - [1/2*z - 1/2, -1/2*z - 1/2] + [-1/2*z - 1/2, 1/2*z - 1/2] sage: K.zeta(4) Traceback (most recent call last): ... - ValueError: There are no 4th roots of unity in self. + ValueError: there are no 4th roots of unity in self :: @@ -7407,7 +7407,7 @@ def zeta(self, n=2, all=False): sage: K.zeta(3) Traceback (most recent call last): ... - ValueError: There are no 3rd roots of unity in self. + ValueError: there are no 3rd roots of unity in self sage: K.zeta(3,all=True) [] @@ -7434,28 +7434,26 @@ def zeta(self, n=2, all=False): if n == 1: if all: return [K.one()] - else: - return K.one() + return K.one() elif n == 2: if all: return [K(-1)] - else: - return K(-1) + return K(-1) - # First check if the degree of K is compatible with an - # inclusion QQ(\zeta_n) -> K. + # First check if the degree of K is compatible + # with an inclusion QQ(\zeta_n) -> K. if sage.arith.all.euler_phi(n).divides(K.absolute_degree()): - # Factor the n-th cyclotomic polynomial over K. - f = K.pari_polynomial('y') - factors = f.nffactor(pari.polcyclo(n)).component(1) - roots = (K(-g.polcoef(0)) for g in factors if g.poldegree() == 1) - if all: - return list(roots) - try: - return next(roots) - except StopIteration: - pass - raise ValueError("There are no %s roots of unity in self." % n.ordinal_str()) + w, zeta_w = self.pari_nf().nfrootsof1() + w = w.sage() + zeta_w = K(zeta_w) + if not w % n: + zeta_n = zeta_w**(w // n) + if all: + return [zeta_n**i for i in n.coprime_integers(n)] + return zeta_n + if all: + return [] + raise ValueError("there are no %s roots of unity in self" % n.ordinal_str()) def zeta_order(self): r""" From d86dd0c79ca6b59dbfae69960fac3a60d0bc8485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 9 Nov 2022 16:45:11 +0100 Subject: [PATCH 377/414] less use of gap.eval in linear_code --- src/sage/coding/linear_code.py | 58 +++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index e8e32f82c95..c19b83779d8 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -586,8 +586,8 @@ def assmus_mattson_designs(self, t, mode=None): if mode=="verbose": for w in nonzerowts: print("The weight w={} codewords of C* form a t-(v,k,lambda) design, where\n \ - t={}, v={}, k={}, lambda={}. \nThere are {} block of this design.".format(\ - w,t,n,w,wts[w]*binomial(w,t)//binomial(n,t),wts[w])) + t={}, v={}, k={}, lambda={}. \nThere are {} block of this design.".format( + w,t,n,w,wts[w]*binomial(w,t)//binomial(n,t),wts[w])) wtsp = Cp.weight_distribution() dp = min([i for i in range(1,len(wtsp)) if wtsp[i]!=0]) nonzerowtsp = [i for i in range(len(wtsp)) if wtsp[i]!=0 and i<=n-t and i>=dp] @@ -595,8 +595,8 @@ def assmus_mattson_designs(self, t, mode=None): if mode=="verbose": for w in nonzerowtsp: print("The weight w={} codewords of C* form a t-(v,k,lambda) design, where\n \ - t={}, v={}, k={}, lambda={}. \nThere are {} block of this design.".format(\ - w,t,n,w,wts[w]*binomial(w,t)//binomial(n,t),wts[w])) + t={}, v={}, k={}, lambda={}. \nThere are {} block of this design.".format( + w,t,n,w,wts[w]*binomial(w,t)//binomial(n,t),wts[w])) if s<=d-t: des = [[t,(n,w,wts[w]*binomial(w,t)//binomial(n,t))] for w in nonzerowts] ans = ans + ["weights from C: ",nonzerowts,"designs from C: ",des] @@ -1370,7 +1370,7 @@ def minimum_distance(self, algorithm=None): # This is done only if algorithm is None. if algorithm not in (None, "gap", "guava"): raise ValueError("The algorithm argument must be one of None, " - "'gap' or 'guava'; got '{0}'".format(algorithm)) + "'gap' or 'guava'; got '{0}'".format(algorithm)) F = self.base_ring() q = F.order() @@ -1380,14 +1380,14 @@ def minimum_distance(self, algorithm=None): "of size at most 256") G = self.generator_matrix() - if (q == 2 or q == 3) and algorithm=="guava": + if (q == 2 or q == 3) and algorithm == "guava": gap.load_package("guava") C = gap(G).GeneratorMatCode(gap(F)) d = C.MinimumWeight() return ZZ(d) return self._minimum_weight_codeword(algorithm).hamming_weight() - def _minimum_weight_codeword(self, algorithm = None): + def _minimum_weight_codeword(self, algorithm=None): r""" Return a minimum weight codeword of ``self``. @@ -1431,7 +1431,7 @@ def _minimum_weight_codeword(self, algorithm = None): current_randstate().set_seed_gap() - if algorithm=="guava": + if algorithm == "guava": GapPackage("guava", spkg="gap_packages").require() gap.load_package("guava") from sage.interfaces.gap import gfq_gap_to_sage @@ -1464,8 +1464,11 @@ def _minimum_weight_codeword(self, algorithm = None): def module_composition_factors(self, gp): r""" - Prints the GAP record of the Meataxe composition factors module in - Meataxe notation. This uses GAP but not Guava. + Print the GAP record of the Meataxe composition factors module. + + This is displayed in Meataxe notation. + + This uses GAP but not Guava. EXAMPLES:: @@ -1473,25 +1476,29 @@ def module_composition_factors(self, gp): sage: G = MS([[1,0,0,0,1,1,1,0],[0,1,1,1,0,0,0,0],[0,0,0,0,0,0,0,1],[0,0,0,0,0,1,0,0]]) sage: C = LinearCode(G) sage: gp = C.permutation_automorphism_group() - - Now type "C.module_composition_factors(gp)" to get the record printed. + sage: C.module_composition_factors(gp) + [ rec( + IsIrreducible := true, + IsOverFiniteField := true, + ...) ] """ + from sage.libs.gap.libgap import libgap F = self.base_ring() - q = F.order() gens = gp.gens() G = self.generator_matrix() n = len(G.columns()) - MS = MatrixSpace(F,n,n) - mats = [] # initializing list of mats by which the gens act on self + MS = MatrixSpace(F, n, n) + mats = [] # initializing list of mats by which the gens act on self Fn = VectorSpace(F, n) - W = Fn.subspace_with_basis(G.rows()) # this is self + W = Fn.subspace_with_basis(G.rows()) # this is self for g in gens: p = MS(g.matrix()) - m = [W.coordinate_vector(r*p) for r in G.rows()] + m = [W.coordinate_vector(r * p) for r in G.rows()] mats.append(m) - mats_str = str(gap([[list(r) for r in m] for m in mats])) - gap.eval("M:=GModuleByMats("+mats_str+", GF("+str(q)+"))") - print(gap("MTX.CompositionFactors( M )")) + mats_gap = libgap(mats) + M_gap = mats_gap.GModuleByMats(F) + compo = libgap.function_factory('MTX.CompositionFactors') + print(compo(M_gap)) def permutation_automorphism_group(self, algorithm="partition"): r""" @@ -1619,7 +1626,7 @@ def permutation_automorphism_group(self, algorithm="partition"): gap.eval("matCwt:=List(Cwt,c->VectorCodeword(c))") # for each i until stop = 1) if gap("Length(matCwt)") > 0: A = gap("MatrixAutomorphisms(matCwt)") - G2 = gap("Intersection2(%s,%s)"%(str(A).replace("\n",""),str(Gp).replace("\n",""))) # bottleneck 3 + G2 = gap("Intersection2(%s,%s)"%(str(A).replace("\n",""),str(Gp).replace("\n",""))) # bottleneck 3 Gp = G2 if Gp.Size()==1: return PermutationGroup([()]) @@ -1830,7 +1837,7 @@ def weight_distribution(self, algorithm=None): q = self.base_ring().order() z = 'Z(%s)*%s'%(q, [0]*self.length()) # GAP zero vector as a string _ = gap.eval("w:=DistancesDistributionMatFFEVecFFE("+Gmat+", GF("+str(q)+"),"+z+")") - v = [eval(gap.eval("w["+str(i)+"]")) for i in range(1,self.length()+2)] # because GAP returns vectors in compressed form + v = [eval(gap.eval("w["+str(i)+"]")) for i in range(1,self.length()+2)] # because GAP returns vectors in compressed form return v elif algorithm=="binary": from sage.coding.binary_code import weight_dist @@ -2144,7 +2151,7 @@ def e(i): return G -############################ linear codes python class ######################## +# ########################### linear codes python class ######################## class LinearCode(AbstractLinearCode): r""" @@ -2773,7 +2780,7 @@ def _build_lookup_table(self): # distance 1 gracefully zero_syndrome = vector(F,[F.zero()]*(n-k)) zero_syndrome.set_immutable() - lookup = { zero_syndrome : vector(F,[F.zero()]*n) } + lookup = {zero_syndrome: vector(F,[F.zero()]*n)} error_position_tables = [cartesian_product([l]*i) for i in range(1, t+1)] first_collision = True #Filling the lookup table @@ -2814,7 +2821,7 @@ def _build_lookup_table(self): # Update decoder types depending on whether we are decoding beyond d/2 if self._code_minimum_distance: if self._maximum_error_weight == (self._code_minimum_distance-1)//2: - self._decoder_type.update({"minimum-distance","always-succeed"}) + self._decoder_type.update({"minimum-distance", "always-succeed"}) else: # then t > (d-1)/2 self._decoder_type.add("might-error") @@ -2822,7 +2829,6 @@ def _build_lookup_table(self): self._decoder_type.add("always-succeed") return lookup - def decode_to_code(self, r): r""" Corrects the errors in ``word`` and returns a codeword. From ec99933aeb934a2310e5436e9b19b4367cb857f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 9 Nov 2022 17:14:57 +0100 Subject: [PATCH 378/414] fix doctest --- src/sage/rings/number_field/order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/number_field/order.py b/src/sage/rings/number_field/order.py index 6eca89ed8dd..22f838a9781 100644 --- a/src/sage/rings/number_field/order.py +++ b/src/sage/rings/number_field/order.py @@ -867,12 +867,12 @@ def zeta(self, n=2, all=False): sage: F. = NumberField(x**2+3) sage: F.ring_of_integers().zeta(6) - 1/2*alpha + 1/2 + -1/2*alpha + 1/2 sage: O = F.order([3*alpha]) sage: O.zeta(3) Traceback (most recent call last): ... - ArithmeticError: There are no 3rd roots of unity in self. + ArithmeticError: there are no 3rd roots of unity in self """ roots_in_field = self.number_field().zeta(n, True) roots_in_self = [self(x) for x in roots_in_field if x in self] @@ -880,7 +880,7 @@ def zeta(self, n=2, all=False): if all: return [] else: - raise ArithmeticError("There are no %s roots of unity in self." % n.ordinal_str()) + raise ArithmeticError("there are no %s roots of unity in self" % n.ordinal_str()) if all: return roots_in_self else: From b50426fcfe2a979d44de50b0069a9ee2d0eecf8d Mon Sep 17 00:00:00 2001 From: Dave Witte Morris Date: Wed, 9 Nov 2022 11:54:23 -0700 Subject: [PATCH 379/414] minor spacing improvement --- src/sage/rings/polynomial/polynomial_element.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 243bbff5553..dc915077cf2 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -11648,7 +11648,7 @@ cdef class Polynomial_generic_dense(Polynomial): sage: R. = P[] sage: f = y^10 + R.random_element(9) sage: g = y^5 + R.random_element(4) - sage: q,r = f.quo_rem(g) + sage: q, r = f.quo_rem(g) sage: f == q*g + r True sage: g = x*y^5 @@ -11669,7 +11669,7 @@ cdef class Polynomial_generic_dense(Polynomial): sage: P. = HH[] sage: f = P.random_element(5) sage: g = P.random_element((0, 5)) - sage: q,r = f.quo_rem(g) + sage: q, r = f.quo_rem(g) sage: f == q*g + r True @@ -11685,7 +11685,7 @@ cdef class Polynomial_generic_dense(Polynomial): sage: h.quo_rem(f) ((-1/13*x^2 - x)*y^2 + (-x^2 + 3*x - 155/4)*y - x - 1, 0) sage: h += (2/3*x^2-3*x+1)*y + 7/17*x+6/5 - sage: q,r = h.quo_rem(f) + sage: q, r = h.quo_rem(f) sage: h == q*f + r and r.degree() < f.degree() True From 09a9c1c9da25b7c0a8c089d5355c742ee3ea014b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 10 Nov 2022 12:19:20 +0100 Subject: [PATCH 380/414] add doctest for fixed bug --- src/sage/rings/number_field/number_field.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 4febd05f45d..4625386d227 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -7417,6 +7417,12 @@ def zeta(self, n=2, all=False): sage: K. = NumberField(1/2*x^2 + 1/6) sage: K.zeta(3) -3/2*a - 1/2 + + TESTS:: + + sage: K = NumberField(x**60+691*x**12-25,'a') + sage: K.zeta(15,all=True) + [] """ try: return self._unit_group.zeta(n, all) From f567abf978ca328f67595743b5248ee42485b41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 10 Nov 2022 14:22:24 +0100 Subject: [PATCH 381/414] get rid of some usage of "long" in pyx files --- .../tutorial-programming-python.rst | 2 +- src/sage/arith/functions.pyx | 2 +- src/sage/arith/long.pxd | 9 ++------- src/sage/crypto/boolean_function.pyx | 4 ++-- .../bounded_integer_sequences.pyx | 4 ++-- src/sage/ext/fast_callable.pyx | 2 +- src/sage/groups/perm_gps/permgroup_element.pyx | 5 ++--- src/sage/libs/singular/function.pyx | 4 +--- src/sage/matrix/args.pyx | 4 ++-- src/sage/matrix/matrix_gfpn_dense.pyx | 2 +- src/sage/matrix/matrix_integer_dense.pyx | 2 +- src/sage/misc/sage_input.py | 10 ++++------ src/sage/modules/free_module_element.pyx | 8 ++++---- src/sage/modules/vector_mod2_dense.pyx | 2 +- src/sage/rings/complex_double.pyx | 2 +- src/sage/rings/complex_mpc.pyx | 4 ++-- src/sage/rings/complex_mpfr.pyx | 2 +- src/sage/rings/finite_rings/element_givaro.pyx | 2 +- .../rings/finite_rings/element_ntl_gf2e.pyx | 2 +- .../rings/finite_rings/element_pari_ffelt.pyx | 2 +- src/sage/rings/integer.pyx | 4 ++-- .../number_field/number_field_element.pyx | 2 +- src/sage/rings/polynomial/pbori/pbori.pyx | 2 +- src/sage/rings/polynomial/plural.pyx | 15 +++------------ .../polynomial_integer_dense_flint.pyx | 4 ++-- src/sage/rings/power_series_ring_element.pyx | 2 +- src/sage/rings/real_mpfi.pyx | 6 +++--- src/sage/rings/real_mpfr.pyx | 4 ++-- src/sage/stats/intlist.pyx | 2 +- src/sage/stats/time_series.pyx | 2 +- src/sage/structure/coerce.pyx | 14 +++++++------- src/sage/symbolic/expression.pyx | 13 ++++++------- src/sage/symbolic/pynac_impl.pxi | 18 +++++++++--------- 33 files changed, 71 insertions(+), 91 deletions(-) diff --git a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst index 50bff7decd2..5cafdf1ea1b 100644 --- a/src/doc/en/thematic_tutorials/tutorial-programming-python.rst +++ b/src/doc/en/thematic_tutorials/tutorial-programming-python.rst @@ -53,7 +53,7 @@ The *standard types* are :class:`bool`, :class:`int`, :class:`list`, * The type :class:`bool` (*booleans*) has two values: ``True`` and ``False``. The boolean operators are denoted by their names ``or``, ``and``, ``not``. -* The Python types :class:`int` and :class:`long` are used to +* The Python type :class:`int` is used to represent integers of limited size. To handle arbitrary large integers with exact arithmetic, Sage uses its own type named :class:`Integer`. diff --git a/src/sage/arith/functions.pyx b/src/sage/arith/functions.pyx index 8d1d25528f8..2fe15bbb974 100644 --- a/src/sage/arith/functions.pyx +++ b/src/sage/arith/functions.pyx @@ -195,7 +195,7 @@ cpdef LCM_list(v): sig_check() if isinstance(elt, Integer): x = elt - elif isinstance(elt, (int, long)): + elif isinstance(elt, int): x = Integer(elt) else: # The result is no longer an Integer, pass to generic code diff --git a/src/sage/arith/long.pxd b/src/sage/arith/long.pxd index b0c80f61480..e3a9f1586e5 100644 --- a/src/sage/arith/long.pxd +++ b/src/sage/arith/long.pxd @@ -194,15 +194,10 @@ cdef inline long dig(const digit* D, int n): cdef inline bint integer_check_long_py(x, long* value, int* err): """ Part of ``integer_check_long`` in ``long.pxd``, checking only for - Python objects of type ``int`` and ``long``. See that function for + Python objects of type ``int``. See that function for documentation and tests. """ - if not isinstance(x, long): - if isinstance(x, int): - # This can happen only on Python 2 - value[0] = PyInt_AS_LONG(x) - err[0] = 0 - return 1 + if not isinstance(x, int): err[0] = ERR_TYPE return 0 diff --git a/src/sage/crypto/boolean_function.pyx b/src/sage/crypto/boolean_function.pyx index 1584ca252ac..a9ea665475c 100644 --- a/src/sage/crypto/boolean_function.pyx +++ b/src/sage/crypto/boolean_function.pyx @@ -321,7 +321,7 @@ cdef class BooleanFunction(SageObject): bitset_set(self._truth_table, i) reed_muller(self._truth_table.bits, ZZ(self._truth_table.limbs).exact_log(2) ) - elif isinstance(x, (int,long,Integer) ): + elif isinstance(x, (int, Integer)): # initialisation to the zero function self._nvariables = ZZ(x) bitset_init(self._truth_table, (1<= self._truth_table.size: raise IndexError("index out of bound") return bitset_in(self._truth_table, x) diff --git a/src/sage/data_structures/bounded_integer_sequences.pyx b/src/sage/data_structures/bounded_integer_sequences.pyx index f307ebd01d1..724f296eae2 100644 --- a/src/sage/data_structures/bounded_integer_sequences.pyx +++ b/src/sage/data_structures/bounded_integer_sequences.pyx @@ -77,7 +77,7 @@ cimported in Cython modules: - ``cdef size_t biseq_getitem_py(biseq_t S, mp_size_t index)`` - Return ``S[index]`` as Python ``int`` or ``long``, without checking margins. + Return ``S[index]`` as Python ``int``, without checking margins. - ``cdef biseq_inititem(biseq_t S, mp_size_t index, size_t item)`` @@ -291,7 +291,7 @@ cdef inline size_t biseq_getitem(biseq_t S, mp_size_t index): cdef biseq_getitem_py(biseq_t S, mp_size_t index): """ - Get item ``S[index]`` as a Python ``int`` or ``long``, without + Get item ``S[index]`` as a Python ``int``, without checking margins. """ diff --git a/src/sage/ext/fast_callable.pyx b/src/sage/ext/fast_callable.pyx index 9cd023601e8..d21bddd5deb 100644 --- a/src/sage/ext/fast_callable.pyx +++ b/src/sage/ext/fast_callable.pyx @@ -983,7 +983,7 @@ cdef class Expression: # flag.) cdef Expression es - if isinstance(o, (int, long, Integer)): + if isinstance(o, (int, Integer)): es = s return ExpressionIPow(es._etb, s, o) else: diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 620ca2a71bd..c0fbd8d15db 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -1061,11 +1061,10 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): # current behavior where if you pass in an integer which # is not in the domain of the permutation group, then that # integer itself will be returned. - if isinstance(i, (long, int, Integer)): + if isinstance(i, (int, Integer)): return i - - if not isinstance(i,(list,tuple,str)): + if not isinstance(i, (list, tuple, str)): raise ValueError("must be in the domain or a list, tuple or string") permuted = [i[self.perm[j]] for j from 0 <= j < self.n] diff --git a/src/sage/libs/singular/function.pyx b/src/sage/libs/singular/function.pyx index 74595b7c367..bb5d38ce80d 100644 --- a/src/sage/libs/singular/function.pyx +++ b/src/sage/libs/singular/function.pyx @@ -1394,9 +1394,7 @@ The Singular documentation for '%s' is given below. ring2 = a.parent() elif is_sage_wrapper_for_singular_ring(a): ring2 = a - elif isinstance(a, int) or\ - isinstance(a, long) or\ - isinstance(a, basestring): + elif isinstance(a, (int, str)): continue elif isinstance(a, Matrix_integer_dense): continue diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 9b7ce991295..9e738312c27 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -1223,7 +1223,7 @@ cdef class MatrixArgs: return self.sequence_type() if isinstance(self.entries, dict): return MA_ENTRIES_MAPPING - if isinstance(self.entries, (int, long, float, complex)): + if isinstance(self.entries, (int, float, complex)): return MA_ENTRIES_SCALAR # Note: some objects are callable, iterable and act like a @@ -1296,7 +1296,7 @@ cdef class MatrixArgs: return MA_ENTRIES_SEQ_SEQ else: return MA_ENTRIES_SEQ_FLAT - if isinstance(x, (int, long, float, complex)): + if isinstance(x, (int, float, complex)): return MA_ENTRIES_SEQ_FLAT if isinstance(x, Element) and element_is_scalar(x): return MA_ENTRIES_SEQ_FLAT diff --git a/src/sage/matrix/matrix_gfpn_dense.pyx b/src/sage/matrix/matrix_gfpn_dense.pyx index e5fdcca4e3f..b3a61ae417e 100644 --- a/src/sage/matrix/matrix_gfpn_dense.pyx +++ b/src/sage/matrix/matrix_gfpn_dense.pyx @@ -1937,7 +1937,7 @@ def mtx_unpickle(f, int nr, int nc, data, bint m): # in the following line, we use a helper function that would return bytes, # regardless whether the input is bytes or str. cdef bytes Data = str_to_bytes(data, encoding='latin1') - if isinstance(f, (int, long)): + if isinstance(f, int): # This is for old pickles created with the group cohomology spkg MS = MatrixSpace(GF(f, 'z'), nr, nc, implementation=Matrix_gfpn_dense) else: diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index 3b8db1ec73f..00292d2e8fa 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -1639,7 +1639,7 @@ cdef class Matrix_integer_dense(Matrix_dense): from .matrix_modn_dense_float import MAX_MODULUS as MAX_MODULUS_FLOAT from .matrix_modn_dense_double import MAX_MODULUS as MAX_MODULUS_DOUBLE - if isinstance(moduli, (int, long, Integer)): + if isinstance(moduli, (int, Integer)): return self._mod_int(moduli) elif isinstance(moduli, list): moduli = MultiModularBasis(moduli) diff --git a/src/sage/misc/sage_input.py b/src/sage/misc/sage_input.py index b49763dbf27..a50bf15d708 100644 --- a/src/sage/misc/sage_input.py +++ b/src/sage/misc/sage_input.py @@ -107,7 +107,7 @@ The ``int`` method on :class:`SageInputBuilder` returns a SIE for an integer that is always represented in the simple way, without coercions. (So, depending on the preparser mode, it might read in as an -:class:`~sage.rings.integer.Integer`, an ``int``, or a ``long``.):: +:class:`~sage.rings.integer.Integer` or an ``int``.):: sage: test_qq_formatter(qq_sage_input_v2) [-ZZ(5)/7, -ZZ(5)/7, -5/7, -5/7, ZZ(3)/1, ZZ(3)/1, 3/1, 3/1] @@ -463,8 +463,6 @@ def __call__(self, x, coerced=False): return SIE_literal_stringrep(self, str(x)) if isinstance(x, int): - # For longs that don't fit in an int, we just use the int - # code; it will get extended to long automatically. if self._preparse is True: if x < 0: return -SIE_literal_stringrep(self, str(-x) + 'r') @@ -548,12 +546,12 @@ def int(self, n): r""" Return a raw SIE from the integer ``n`` - As it is raw, it may read back as a Sage Integer, a Python int or a - Python long, depending on its size and whether the preparser is enabled. + As it is raw, it may read back as a Sage Integer or a Python int, + depending on its size and whether the preparser is enabled. INPUT: - - ``n`` - a Sage Integer, a Python int or a Python long + - ``n`` -- a Sage Integer or a Python int EXAMPLES:: diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 67991cebd52..c1e1424bfe8 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -371,7 +371,7 @@ def vector(arg0, arg1=None, arg2=None, sparse=None, immutable=False): ... TypeError: cannot convert 2-dimensional array to a vector - If any of the arguments to vector have Python type int, long, real, + If any of the arguments to vector have Python type int, real, or complex, they will first be coerced to the appropriate Sage objects. This fixes :trac:`3847`. :: @@ -475,7 +475,7 @@ def vector(arg0, arg1=None, arg2=None, sparse=None, immutable=False): # We first efficiently handle the important special case of the zero vector # over a ring. See trac 11657. # !! PLEASE DO NOT MOVE THIS CODE LOWER IN THIS FUNCTION !! - arg1_integer = isinstance(arg1, (int, long, Integer)) + arg1_integer = isinstance(arg1, (int, Integer)) if arg2 is None and is_Ring(arg0) and arg1_integer: M = FreeModule(arg0, arg1, bool(sparse)) v = M.zero_vector() @@ -898,14 +898,14 @@ def random_vector(ring, degree=None, *args, **kwds): ... ValueError: degree of a random vector must be non-negative, not -9 """ - if isinstance(ring, (Integer, int, long)): + if isinstance(ring, (Integer, int)): if not degree is None: arglist = list(args) arglist.insert(0, degree) args = tuple(arglist) degree = ring ring = ZZ - if not isinstance(degree,(Integer, int, long)): + if not isinstance(degree, (Integer, int)): raise TypeError("degree of a random vector must be an integer, not %s" % degree) if degree < 0: raise ValueError("degree of a random vector must be non-negative, not %s" % degree) diff --git a/src/sage/modules/vector_mod2_dense.pyx b/src/sage/modules/vector_mod2_dense.pyx index 9d57897febe..370e7040cbd 100644 --- a/src/sage/modules/vector_mod2_dense.pyx +++ b/src/sage/modules/vector_mod2_dense.pyx @@ -200,7 +200,7 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): raise TypeError("x must be a list of the right length") for i in range(len(x)): xi = x[i] - if isinstance(xi, (IntegerMod_int, int, long, Integer)): + if isinstance(xi, (IntegerMod_int, int, Integer)): # the if/else statement is because in some compilers, (-1)%2 is -1 mzd_write_bit(self._entries, 0, i, 1 if xi%2 else 0) elif isinstance(xi, Rational): diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index f0c70c67f82..af568e65e17 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -380,7 +380,7 @@ cdef class ComplexDoubleField_class(sage.rings.abc.ComplexDoubleField): return x elif isinstance(x, tuple): return ComplexDoubleElement(x[0], x[1]) - elif isinstance(x, (float, int, long)): + elif isinstance(x, (float, int)): return ComplexDoubleElement(x, 0) elif isinstance(x, complex): return ComplexDoubleElement(x.real, x.imag) diff --git a/src/sage/rings/complex_mpc.pyx b/src/sage/rings/complex_mpc.pyx index 2a5e3048fbc..5de92be1a3d 100644 --- a/src/sage/rings/complex_mpc.pyx +++ b/src/sage/rings/complex_mpc.pyx @@ -869,7 +869,7 @@ cdef class MPComplexNumber(sage.structure.element.FieldElement): elif isinstance(z, Integer): mpc_set_z(self.value, (z).value, rnd) return - elif isinstance(z, (int, long)): + elif isinstance(z, int): mpc_set_si(self.value, z, rnd) return else: @@ -1623,7 +1623,7 @@ cdef class MPComplexNumber(sage.structure.element.FieldElement): x = self z = x._new() - if isinstance(right, (int,long)): + if isinstance(right, int): mpc_pow_si(z.value, x.value, right, (x._parent).__rnd) elif isinstance(right, Integer): mpc_pow_z(z.value, x.value, (right).value, (x._parent).__rnd) diff --git a/src/sage/rings/complex_mpfr.pyx b/src/sage/rings/complex_mpfr.pyx index 5083f66a89d..dd90e39488c 100644 --- a/src/sage/rings/complex_mpfr.pyx +++ b/src/sage/rings/complex_mpfr.pyx @@ -1714,7 +1714,7 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): sage: float(5)^(0.5 + 14.1347251*I) -1.62414637645790 - 1.53692828324508*I """ - if isinstance(right, (int, long, Integer)): + if isinstance(right, (int, Integer)): return RingElement.__pow__(self, right) try: diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index 930eccbe57b..342c5f08090 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -378,7 +378,7 @@ cdef class Cache_givaro(Cache_base): else: raise TypeError("unable to coerce from a finite field other than the prime subfield") - elif isinstance(e, (int, Integer, long)) or is_IntegerMod(e): + elif isinstance(e, (int, Integer) or is_IntegerMod(e): try: e_int = e % self.characteristic() self.objectptr.initi(res, e_int) diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index 96e4e3df8d9..c36adac770f 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -434,7 +434,7 @@ cdef class Cache_ntl_gf2e(Cache_base): if number < 0 or number >= self._order: raise TypeError("n must be between 0 and self.order()") - if isinstance(number, int) or isinstance(number, long): + if isinstance(number, int): if not number: n = 0 else: diff --git a/src/sage/rings/finite_rings/element_pari_ffelt.pyx b/src/sage/rings/finite_rings/element_pari_ffelt.pyx index ee0f9b3c4f5..3bf5c62ff32 100644 --- a/src/sage/rings/finite_rings/element_pari_ffelt.pyx +++ b/src/sage/rings/finite_rings/element_pari_ffelt.pyx @@ -395,7 +395,7 @@ cdef class FiniteFieldElement_pari_ffelt(FinitePolyExtElement): x_GEN = _new_GEN_from_mpz_t((x).value) self.construct(_INT_to_FFELT(g, x_GEN)) - elif isinstance(x, int) or isinstance(x, long): + elif isinstance(x, int): g = (self._parent._gen_pari).g x = objtogen(x) sig_on() diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 0312f3f99fb..be8d08412fd 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -1184,7 +1184,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): .. NOTE:: '0x' is *not* prepended to the result like is done by the - corresponding Python function on ``int`` or ``long``. This is for + corresponding Python function on ``int``. This is for efficiency sake--adding and stripping the string wastes time; since this function is used for conversions from integers to other C-library structures, it is important @@ -1208,7 +1208,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): .. NOTE:: '0' (or '0o') is *not* prepended to the result like is done by the - corresponding Python function on ``int`` or ``long``. This is for + corresponding Python function on ``int``. This is for efficiency sake--adding and stripping the string wastes time; since this function is used for conversions from integers to other C-library structures, it is important diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index b78bc33078c..30f9c8ba57f 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -308,7 +308,7 @@ cdef class NumberFieldElement(FieldElement): self.__fld_numerator, self.__fld_denominator = parent.absolute_polynomial_ntl() cdef ZZ_c coeff - if isinstance(f, (int, long, Integer_sage)): + if isinstance(f, (int, Integer_sage)): # set it up and exit immediately # fast pathway mpz_to_ZZ(&coeff, (ZZ(f)).value) diff --git a/src/sage/rings/polynomial/pbori/pbori.pyx b/src/sage/rings/polynomial/pbori/pbori.pyx index 41850904621..153e69854a7 100644 --- a/src/sage/rings/polynomial/pbori/pbori.pyx +++ b/src/sage/rings/polynomial/pbori/pbori.pyx @@ -2213,7 +2213,7 @@ class BooleanMonomialMonoid(UniqueRepresentation, Monoid_class): self.base_ring().has_coerce_map_from(other.parent()) and \ self.base_ring()(other).is_one(): return self._one_element - elif isinstance(other, (int, long)) and other % 2: + elif isinstance(other, int) and other % 2: return self._one_element elif isinstance(other, (list, set)): diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 9a996550975..c0931168995 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -569,19 +569,10 @@ cdef class NCPolynomialRing_plural(Ring): _n = sa2si(base_ring(element), _ring) _p = p_NSet(_n, _ring) - # and longs - elif isinstance(element, long): - if isinstance(base_ring, FiniteField_prime_modn): - element = element % self.base_ring().characteristic() - _p = p_ISet(int(element),_ring) - else: - _n = sa2si(base_ring(element),_ring) - _p = p_NSet(_n, _ring) - else: - raise NotImplementedError("not able to interpret "+repr(element) + - " of type "+ repr(type(element)) + - " as noncommutative polynomial") ### ?????? + raise NotImplementedError(f"not able to interpret {element)}" + f" of type {type(element)}" + " as noncommutative polynomial") # ???? return new_NCP(self, _p) cpdef _coerce_map_from_(self, S): diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx index bde23684615..2df63d29c14 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx @@ -367,7 +367,7 @@ cdef class Polynomial_integer_dense_flint(Polynomial): interpreted as polynomial composition or evaluation by this method. - If the argument is not simply an integer (``int``, ``long`` or + If the argument is not simply an integer (``int`` or ``Integer``) a real number (``RealNumber``) a real interval (``RealIntervalFieldElement``) or a polynomial (of the same type as ``self``), the call is passed on to the generic implementation in the @@ -428,7 +428,7 @@ cdef class Polynomial_integer_dense_flint(Polynomial): fmpz_clear(z_fmpz) sig_off() return z - if isinstance(x0, (int, long)): + if isinstance(x0, int): x0 = Integer(x0) if isinstance(x0, Integer): a = x0 diff --git a/src/sage/rings/power_series_ring_element.pyx b/src/sage/rings/power_series_ring_element.pyx index 7cf46f7f170..2422521fba8 100644 --- a/src/sage/rings/power_series_ring_element.pyx +++ b/src/sage/rings/power_series_ring_element.pyx @@ -1119,7 +1119,7 @@ cdef class PowerSeries(AlgebraElement): T^2 + O(T^3) """ from sage.rings.power_series_ring import PowerSeriesRing - if isinstance(other, (int, Integer, long)): + if isinstance(other, (int, Integer)): return PowerSeriesRing(IntegerModRing(other), self.variable())(self) raise NotImplementedError("Mod on power series ring elements not defined except modulo an integer.") diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index a9e4b1eed16..5f1494000f7 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -2912,7 +2912,7 @@ cdef class RealIntervalFieldElement(RingElement): sage: RIF(1.0) << 32 4294967296 """ - if isinstance(x, RealIntervalFieldElement) and isinstance(y, (int,long, Integer)): + if isinstance(x, RealIntervalFieldElement) and isinstance(y, (int, Integer)): return x._lshift_(y) return sage.structure.element.bin_op(x, y, operator.lshift) @@ -2944,7 +2944,7 @@ cdef class RealIntervalFieldElement(RingElement): 0.062500000000000000? """ if isinstance(x, RealIntervalFieldElement) and \ - isinstance(y, (int,long,Integer)): + isinstance(y, (int, Integer)): return x._rshift_(y) return sage.structure.element.bin_op(x, y, operator.rshift) @@ -4411,7 +4411,7 @@ cdef class RealIntervalFieldElement(RingElement): """ if exponent == 2: return self.square() - if isinstance(exponent, (int, long, Integer)): + if isinstance(exponent, (int, Integer)): q, r = divmod (exponent, 2) if r == 0: # x^(2q) = (x^q)^2 xq = RingElement.__pow__(self, q) diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index c9214785c3c..09ace5ec1eb 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -678,7 +678,7 @@ cdef class RealField_class(sage.rings.abc.RealField): - Any MPFR real field with precision that is as large as this one - - int, long, integer, and rational rings. + - int, integer, and rational rings. - the field of algebraic reals @@ -6074,7 +6074,7 @@ cdef class int_toRR(Map): cdef long x_long cdef mpz_t x_mpz - if not isinstance(x, (int, long)): + if not isinstance(x, int): x = int(x) integer_check_long_py(x, &x_long, &err) diff --git a/src/sage/stats/intlist.pyx b/src/sage/stats/intlist.pyx index 6de4f36d70c..f29d6519d97 100644 --- a/src/sage/stats/intlist.pyx +++ b/src/sage/stats/intlist.pyx @@ -95,7 +95,7 @@ cdef class IntList: [1, -2] """ cdef TimeSeries T - if isinstance(values, (int,long,Integer)): + if isinstance(values, (int, Integer)): self._length = values values = None elif isinstance(values, TimeSeries): diff --git a/src/sage/stats/time_series.pyx b/src/sage/stats/time_series.pyx index 2624f7ebf2a..c90ea577bc8 100644 --- a/src/sage/stats/time_series.pyx +++ b/src/sage/stats/time_series.pyx @@ -128,7 +128,7 @@ cdef class TimeSeries: cdef cnumpy.ndarray np cdef double *np_data cdef unsigned int j - if isinstance(values, (int, long, Integer)): + if isinstance(values, (int, Integer)): self._length = values values = None elif isinstance(values, (Vector_real_double_dense, cnumpy.ndarray)): diff --git a/src/sage/structure/coerce.pyx b/src/sage/structure/coerce.pyx index 227a62cb3ef..86664258c8c 100644 --- a/src/sage/structure/coerce.pyx +++ b/src/sage/structure/coerce.pyx @@ -150,7 +150,7 @@ cpdef py_scalar_parent(py_type): sage: py_scalar_parent(gmpy2.mpc) Complex Double Field """ - if issubclass(py_type, int) or issubclass(py_type, long): + if issubclass(py_type, int): import sage.rings.integer_ring return sage.rings.integer_ring.ZZ if py_type is FractionType: @@ -266,7 +266,7 @@ cpdef py_scalar_to_element(x): """ if isinstance(x, Element): return x - elif isinstance(x, (int, long)): + elif isinstance(x, int): from sage.rings.integer import Integer return Integer(x) elif type(x) is FractionType: @@ -344,7 +344,7 @@ cpdef bint parent_is_integers(P) except -1: 2*f """ if isinstance(P, type): - if issubclass(P, int) or issubclass(P, long): + if issubclass(P, int): return True elif is_numpy_type(P): from numpy import integer @@ -969,7 +969,7 @@ cdef class CoercionModel: res = self.division_parent(res) return all, res - if isinstance(yp, Parent) and xp in [int, long, float, complex, bool]: + if isinstance(yp, Parent) and xp in [int, float, complex, bool]: mor = yp._internal_coerce_map_from(xp) if mor is not None: mor = mor.__copy__() @@ -982,7 +982,7 @@ cdef class CoercionModel: elif type(xp) is type: all.append("Left operand is not Sage element, will try _sage_.") - if isinstance(xp, Parent) and yp in [int, long, float, complex, bool]: + if isinstance(xp, Parent) and yp in [int, float, complex, bool]: mor = xp._internal_coerce_map_from(yp) if mor is not None: mor = mor.__copy__() @@ -1331,8 +1331,8 @@ cdef class CoercionModel: return x_elt,y_elt self._coercion_error(x, x_map, x_elt, y, y_map, y_elt) - cdef bint x_numeric = isinstance(x, (int, long, float, complex)) - cdef bint y_numeric = isinstance(y, (int, long, float, complex)) + cdef bint x_numeric = isinstance(x, (int, float, complex)) + cdef bint y_numeric = isinstance(y, (int, float, complex)) if not x_numeric and is_numpy_type(type(x)): import numpy diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 903a8541460..da90a64c885 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -4712,7 +4712,7 @@ cdef class Expression(Expression_abc): return self.gradient() else: raise ValueError("No differentiation variable specified.") - if not isinstance(deg, (int, long, sage.rings.integer.Integer)) \ + if not isinstance(deg, (int, sage.rings.integer.Integer)) \ or deg < 1: raise TypeError("argument deg should be an integer >= 1.") cdef Expression symbol = self.coerce_in(symb) @@ -11290,10 +11290,11 @@ cdef class Expression(Expression_abc): def canonicalize_radical(self): r""" - Choose a canonical branch of the given expression. The square - root, cube root, natural log, etc. functions are multi-valued. The - ``canonicalize_radical()`` method will choose *one* of these values - based on a heuristic. + Choose a canonical branch of the given expression. + + The square root, cube root, natural log, etc. functions are + multi-valued. The ``canonicalize_radical()`` method will + choose *one* of these values based on a heuristic. For example, ``sqrt(x^2)`` has two values: ``x``, and ``-x``. The ``canonicalize_radical()`` function will choose @@ -13722,8 +13723,6 @@ cpdef new_Expression(parent, x): from sage.symbolic.constants import NaN return NaN exp = x - elif isinstance(x, long): - exp = x elif isinstance(x, int): exp = GEx(x) elif x is infinity: diff --git a/src/sage/symbolic/pynac_impl.pxi b/src/sage/symbolic/pynac_impl.pxi index d5934b565bc..0af6aae7fe1 100644 --- a/src/sage/symbolic/pynac_impl.pxi +++ b/src/sage/symbolic/pynac_impl.pxi @@ -1018,7 +1018,7 @@ cdef py_real(x): sage: py_real(complex(2,2)) 2.0 """ - if isinstance(x, (float, int, long)): + if isinstance(x, (float, int)): return x elif isinstance(x, complex): return x.real @@ -1118,7 +1118,7 @@ cdef py_conjugate(x): cdef bint py_is_rational(x): return (type(x) is Rational or type(x) is Integer or - isinstance(x, (int, long))) + isinstance(x, int)) cdef bint py_is_equal(x, y): @@ -1156,7 +1156,7 @@ cdef bint py_is_integer(x): sage: py_is_integer(3.0r) False """ - if isinstance(x, (int, long, Integer)): + if isinstance(x, (int, Integer)): return True if not isinstance(x, Element): return False @@ -1220,7 +1220,7 @@ def py_is_crational_for_doctest(x): cdef bint py_is_real(a): - if isinstance(a, (int, long, Integer, float)): + if isinstance(a, (int, Integer, float)): return True try: P = parent(a) @@ -1246,7 +1246,7 @@ cdef bint py_is_prime(n): cdef bint py_is_exact(x): - if isinstance(x, (int, long, Integer)): + if isinstance(x, (int, Integer)): return True if not isinstance(x, Element): return False @@ -1281,7 +1281,7 @@ cdef py_numer(n): sage: py_numer(no_numer()) 42 """ - if isinstance(n, (int, long, Integer)): + if isinstance(n, (int, Integer)): return n try: return n.numerator() @@ -1319,7 +1319,7 @@ cdef py_denom(n): sage: py_denom(2/3*i) 3 """ - if isinstance(n, (int, long, Integer)): + if isinstance(n, (int, Integer)): return 1 try: return n.denominator() @@ -1441,7 +1441,7 @@ cdef py_tgamma(x): sage: py_tgamma(1/2) 1.77245385090552 """ - if isinstance(x, (int, long)): + if isinstance(x, int): x = float(x) if type(x) is float: return math.tgamma(PyFloat_AS_DOUBLE(x)) @@ -1759,7 +1759,7 @@ cdef py_log(x): """ cdef gsl_complex res cdef double real, imag - if isinstance(x, (int, long)): + if isinstance(x, int): x = float(x) if type(x) is float: real = PyFloat_AS_DOUBLE(x) From 605968da44842a6351f93f69dbe68088ac9d749c Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Thu, 10 Nov 2022 17:06:38 +0000 Subject: [PATCH 382/414] numerical noise fixes we also made some tests use norm(), and shortened inputs using variables --- src/sage/geometry/polyhedron/library.py | 24 ++++++------------- src/sage/graphs/generic_graph.py | 2 +- .../schemes/projective/projective_space.py | 2 +- .../riemann_surfaces/riemann_surface.py | 6 ++--- .../polynomes_doctest.py | 2 +- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/sage/geometry/polyhedron/library.py b/src/sage/geometry/polyhedron/library.py index 880eb4ee008..e54d8a7efd1 100644 --- a/src/sage/geometry/polyhedron/library.py +++ b/src/sage/geometry/polyhedron/library.py @@ -391,38 +391,28 @@ def gale_transform_to_primal(vectors, base_ring=None, backend=None): One can specify the base ring:: - sage: gale_transform_to_primal( - ....: [(1,1), (-1,-1), (1,0), - ....: (-1,0), (1,-1), (-2,1)]) + sage: p = [(1,1), (-1,-1), (1,0), (-1,0), (1,-1), (-2,1)] + sage: gtpp = gale_transform_to_primal(p); gtpp [(16, -35, 54), (24, 10, 31), (-15, 50, -60), (-25, 0, 0), (0, -25, 0), (0, 0, -25)] - sage: gale_transform_to_primal( - ....: [(1,1),(-1,-1),(1,0),(-1,0),(1,-1),(-2,1)], base_ring=RDF) - [(-0.6400000000000001, 1.4, -2.1600000000000006), - (-0.9600000000000002, -0.39999999999999997, -1.2400000000000002), - (0.6000000000000001, -2.0, 2.4000000000000004), - (1.0, 0.0, 0.0), - (0.0, 1.0, 0.0), - (0.0, 0.0, 1.0)] + sage: (matrix(RDF, gtpp)/25 + + ....: matrix(gale_transform_to_primal(p, base_ring=RDF))).norm() < 1e-15 + True One can also specify the backend to be used internally:: - sage: gale_transform_to_primal( - ....: [(1,1), (-1,-1), (1,0), - ....: (-1,0), (1,-1), (-2,1)], backend='field') + sage: gale_transform_to_primal(p, backend='field') [(48, -71, 88), (84, -28, 99), (-77, 154, -132), (-55, 0, 0), (0, -55, 0), (0, 0, -55)] - sage: gale_transform_to_primal( # optional - pynormaliz - ....: [(1,1), (-1,-1), (1,0), - ....: (-1,0), (1,-1), (-2,1)], backend='normaliz') + sage: gale_transform_to_primal(p, backend='normaliz') # optional - pynormaliz [(16, -35, 54), (24, 10, 31), (-15, 50, -60), diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index f2c94041c51..be31830500a 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -24154,7 +24154,7 @@ def katz_centrality(self, alpha, u=None): other nodes. :: sage: G = DiGraph({1: [10], 2:[10,11], 3:[10,11], 4:[], 5:[11, 4], 6:[11], 7:[10,11], 8:[10,11], 9:[10], 10:[11, 5, 8], 11:[6]}) - sage: G.katz_centrality(.85) + sage: G.katz_centrality(.85) # rel tol 1e-14 {1: 0.000000000000000, 2: 0.000000000000000, 3: 0.000000000000000, diff --git a/src/sage/schemes/projective/projective_space.py b/src/sage/schemes/projective/projective_space.py index eeb5dc589ca..2813bf4de24 100644 --- a/src/sage/schemes/projective/projective_space.py +++ b/src/sage/schemes/projective/projective_space.py @@ -1392,7 +1392,7 @@ def point_transformation_matrix(self, points_source, points_target, normalize=Tr sage: P1.=ProjectiveSpace(RR, 2) sage: points_source=[P1([1, 4, 1]), P1([1, 2, 2]), P1([3, 5, 1]), P1([1, -1, 1])] sage: points_target=[P1([5, -2, 7]), P1([3, -2, 3]), P1([6, -5, 9]), P1([3, 6, 7])] - sage: P1.point_transformation_matrix(points_source, points_target) + sage: P1.point_transformation_matrix(points_source, points_target) # abs tol 1e-13 [-0.0619047619047597 -0.609523809523810 -0.119047619047621] [ 0.853968253968253 0.0380952380952380 0.0412698412698421] [ -0.214285714285712 -0.933333333333333 0.280952380952379] diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index facd90d0425..b219cb975e2 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -268,9 +268,9 @@ def numerical_inverse(C): ....: -0.091587 + 0.19276*I, ....: 3.9443e-31 + 0.38552*I]) sage: from sage.schemes.riemann_surfaces.riemann_surface import numerical_inverse - sage: max(abs(c) for c in (C^(-1)*C-C^0).list()) < 1e-10 - False - sage: max(abs(c) for c in (numerical_inverse(C)*C-C^0).list()) < 1e-10 + sage: 3e-16 < (C^-1*C-C^0).norm() < 1e-15 + True + sage: (numerical_inverse(C)*C-C^0).norm() < 3e-16 True """ R = C.parent() diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py index 9b3eade3687..219afcd22fd 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py @@ -257,7 +257,7 @@ Sage example in ./polynomes.tex, line 1531:: sage: C = ComplexField(15) - sage: Frac(C['x'])(r).partial_fraction_decomposition() + sage: Frac(C['x'])(r).partial_fraction_decomposition() #abs tol 2e-4 (x^4 - x^2 + 6.000, [0.5312/(x - 1.000), 0.06250/(x^2 - 2.000*x + 1.000), 4.385*I/(x - 1.732*I), (-4.385*I)/(x + 1.732*I), (-0.5312)/(x + 1.000), 0.06250/(x^2 + 2.000*x + 1.000)]) From e8cebc3b2a473343d3a07c5d5b6102265f265b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 10 Nov 2022 18:25:34 +0100 Subject: [PATCH 383/414] fix typo --- src/sage/rings/finite_rings/element_givaro.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/finite_rings/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index 342c5f08090..920fdf273fc 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -378,7 +378,7 @@ cdef class Cache_givaro(Cache_base): else: raise TypeError("unable to coerce from a finite field other than the prime subfield") - elif isinstance(e, (int, Integer) or is_IntegerMod(e): + elif isinstance(e, (int, Integer)) or is_IntegerMod(e): try: e_int = e % self.characteristic() self.objectptr.initi(res, e_int) From 39ad89060c6931db6049620fdf9dc408941abb3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 11 Nov 2022 08:24:17 +0100 Subject: [PATCH 384/414] fix factorial of type int --- src/sage/functions/other.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index 1ce7569919f..eff27f95bc6 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -1518,7 +1518,7 @@ def _eval_(self, x): sage: type(factorial(float(3.2))) """ - if isinstance(x, Integer): + if isinstance(x, (int, Integer)): try: return x.factorial() except OverflowError: From 966f1596adefde7a85bab6c8c5b3f4b67537216f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 11 Nov 2022 17:09:48 +0100 Subject: [PATCH 385/414] small fixes --- src/sage/rings/polynomial/plural.pyx | 4 ++-- src/sage/symbolic/expression.pyx | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index c0931168995..3209824ec88 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -570,9 +570,9 @@ cdef class NCPolynomialRing_plural(Ring): _p = p_NSet(_n, _ring) else: - raise NotImplementedError(f"not able to interpret {element)}" + raise NotImplementedError(f"not able to interpret {element}" f" of type {type(element)}" - " as noncommutative polynomial") # ???? + " as noncommutative polynomial") # ??? return new_NCP(self, _p) cpdef _coerce_map_from_(self, S): diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index da90a64c885..e59469135f0 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -13724,7 +13724,10 @@ cpdef new_Expression(parent, x): return NaN exp = x elif isinstance(x, int): - exp = GEx(x) + try: + exp = GEx(x) + except OverflowError: + exp = x elif x is infinity: return new_Expression_from_GEx(parent, g_Infinity) elif x is minus_infinity: From f185d689989a661cf77e22471a56277f61a18dd5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 11 Nov 2022 13:48:30 -0800 Subject: [PATCH 386/414] .github/workflows/ci-macos.yml: Update tested macos versions, remove test of outdated python from python.org --- .github/workflows/ci-macos.yml | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index d2df2268199..1921fc0d066 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -42,12 +42,8 @@ jobs: # python3_xcode is only accepted if enough packages are available from the system # --> to test "minimal", we would need https://trac.sagemath.org/ticket/30949 tox_env: [homebrew-macos-usrlocal-minimal, homebrew-macos-usrlocal-standard, homebrew-macos-usrlocal-maximal, homebrew-macos-usrlocal-python3_xcode-standard, conda-forge-macos-minimal, conda-forge-macos-standard, conda-forge-macos-maximal] - # As of 2021-12, default xcode - # - on macos-10.15: 12.4 - # - on macos-latest (= macos-11): 13.1 - # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md#xcode xcode_version_factor: [default] - os: [ macos-10.15, macos-latest ] + os: [ macos-11, macos-12 ] env: TOX_ENV: local-${{ matrix.tox_env }} LOCAL_ARTIFACT_NAME: sage-local-commit-${{ github.sha }}-tox-local-${{ matrix.tox_env }}-${{ matrix.os }}-xcode_${{ matrix.xcode_version_factor }} @@ -158,22 +154,10 @@ jobs: fail-fast: false max-parallel: 4 matrix: - os: [ macos-10.15, macos-11.0 ] - tox_system_factor: [macos-nobootstrap, macos-nobootstrap-python3_pythonorg] + os: [ macos-11, macos-12 ] + tox_system_factor: [macos-nobootstrap] tox_packages_factor: [minimal] - # As of 2021-03, default is 12.4 - # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md#xcode xcode_version_factor: [default] - include: - # Test xcode 11.7 only on macos-10.15 - - tox_system_factor: macos-nobootstrap - tox_packages_factor: minimal - xcode_version_factor: 11.7 - os: macos-10.15 - - tox_system_factor: macos-nobootstrap-python3_pythonorg - tox_packages_factor: minimal - xcode_version_factor: 11.7 - os: macos-10.15 env: TOX_ENV: local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }}-xcode_${{ matrix.xcode_version_factor }} @@ -198,13 +182,6 @@ jobs: - name: Install test prerequisites run: | sudo /usr/bin/python3 -m pip install tox - - name: Install python3 from python.org - # As of 2020-03-30 (https://github.com/actions/virtual-environments/blob/master/images/macos/macos-10.15-Readme.md), - # Python 3.7.7 is installed on GitHub Actions runners. But we install our own copy from the python.org binary package. - run: | - curl -o python3.pkg https://www.python.org/ftp/python/3.7.7/python-3.7.7-macosx10.9.pkg - sudo installer -verbose -pkg python3.pkg -target / - if: contains(matrix.tox_system_factor, 'python3_pythonorg') - name: Build and test with tox # We use a high parallelization on purpose in order to catch possible parallelization bugs in the build scripts. # For doctesting, we use a lower parallelization to avoid timeouts. From 278058b3ac0d3862c82c431d1c6dbe4dbdd8ed8c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 11 Nov 2022 19:06:22 -0800 Subject: [PATCH 387/414] build/pkgs/topcom/spkg-install.in: Do not run autotools --- build/pkgs/topcom/spkg-install.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/pkgs/topcom/spkg-install.in b/build/pkgs/topcom/spkg-install.in index 53eb50b1b98..561925248d7 100644 --- a/build/pkgs/topcom/spkg-install.in +++ b/build/pkgs/topcom/spkg-install.in @@ -1,4 +1,9 @@ cd src + +# Avoid running autotools. Patch no_vendor_cddlib.patch makes changes +# both to input and output. +touch configure Makefile.in + ./configure \ --prefix="$SAGE_LOCAL" \ --libdir="$SAGE_LOCAL/lib" \ From 91704bbd12bdb01c4137d1d0dd51b68499258d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 12 Nov 2022 09:34:06 +0100 Subject: [PATCH 388/414] switch back to --- src/sage/symbolic/expression.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index e59469135f0..5efc0d792bc 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -13725,7 +13725,7 @@ cpdef new_Expression(parent, x): exp = x elif isinstance(x, int): try: - exp = GEx(x) + exp = GEx(x) except OverflowError: exp = x elif x is infinity: From 49259be43ec5f1872948e1a183c4878f19ce2d2a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 12 Nov 2022 14:44:38 -0800 Subject: [PATCH 389/414] SageIpythonConfiguration.colors: Use default from SageTerminalInteractiveShell instead of forcing LightBG --- src/sage/repl/configuration.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/repl/configuration.py b/src/sage/repl/configuration.py index 1f2aeb27692..99e51699868 100644 --- a/src/sage/repl/configuration.py +++ b/src/sage/repl/configuration.py @@ -81,7 +81,10 @@ def colors(self): sage: sage_ipython_config.simple_prompt() True """ - return 'LightBG' if self._allow_ansi() else 'NoColor' + if not self._allow_ansi(): + return 'NoColor' + from sage.repl.interpreter import SageTerminalInteractiveShell + return SageTerminalInteractiveShell.colors.default() def simple_prompt(self): """ From 1801f626544ccd1f112c6e6ac3527a6b99920fad Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Sep 2022 10:14:51 -0700 Subject: [PATCH 390/414] VectorFieldModule: Add tensor_product, tensor_power --- .../differentiable/diff_form_module.py | 28 +++++ .../differentiable/tensorfield_module.py | 6 + .../differentiable/vectorfield_module.py | 116 +++++++++++++++++- 3 files changed, 144 insertions(+), 6 deletions(-) diff --git a/src/sage/manifolds/differentiable/diff_form_module.py b/src/sage/manifolds/differentiable/diff_form_module.py index 37feeaa7650..4b9a7cae78c 100644 --- a/src/sage/manifolds/differentiable/diff_form_module.py +++ b/src/sage/manifolds/differentiable/diff_form_module.py @@ -47,6 +47,7 @@ from sage.manifolds.differentiable.diff_form import DiffForm, DiffFormParal from sage.manifolds.differentiable.tensorfield import TensorField from sage.manifolds.differentiable.tensorfield_paral import TensorFieldParal +from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule class DiffFormModule(UniqueRepresentation, Parent): @@ -534,6 +535,33 @@ def base_module(self): """ return self._vmodule + def tensor_type(self): + r""" + Return the tensor type of ``self`` if ``self`` is a module of 1-forms. + + In this case, the pair `(0, 1)` is returned, indicating that the module + is identified with the dual of the base module. + + For differential forms of other degrees, an exception is raised. + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: M.diff_form_module(1).tensor_type() + (0, 1) + sage: M.diff_form_module(2).tensor_type() + Traceback (most recent call last): + ... + NotImplementedError + """ + if self._degree == 1: + return (0, 1) + raise NotImplementedError + + tensor_power = VectorFieldModule.tensor_power + + tensor = tensor_product = VectorFieldModule.tensor_product + def degree(self): r""" Return the degree of the differential forms in ``self``. diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index cbc73f9e520..d845ae03766 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -50,6 +50,8 @@ MultivectorFieldParal) from sage.manifolds.differentiable.automorphismfield import (AutomorphismField, AutomorphismFieldParal) +from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule + class TensorFieldModule(UniqueRepresentation, Parent): r""" @@ -561,6 +563,10 @@ def tensor_type(self): """ return self._tensor_type + tensor_power = VectorFieldModule.tensor_power + + tensor = tensor_product = VectorFieldModule.tensor_product + @cached_method def zero(self): """ diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 580eec814ab..8c023117eab 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -506,6 +506,34 @@ def destination_map(self): """ return self._dest_map + def base_module(self): + r""" + Return the module on which ``self`` is constructed, namely ``self`` itself. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: XM = M.vector_field_module() + sage: XM.base_module() is XM + True + + """ + return self + + def tensor_type(self): + r""" + Return the tensor type of ``self``, the pair `(1, 0)`. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: XM = M.vector_field_module() + sage: XM.tensor_type() + (1, 0) + + """ + return (1, 0) + def tensor_module(self, k, l, *, sym=None, antisym=None): r""" Return the module of type-`(k,l)` tensors on ``self``. @@ -731,8 +759,8 @@ def general_linear_group(self): self._general_linear_group = AutomorphismFieldGroup(self) return self._general_linear_group - def tensor(self, tensor_type, name=None, latex_name=None, sym=None, - antisym=None, specific_type=None): + def _tensor(self, tensor_type, name=None, latex_name=None, sym=None, + antisym=None, specific_type=None): r""" Construct a tensor on ``self``. @@ -776,10 +804,6 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, sage: XM.tensor((1,2), name='t') Tensor field t of type (1,2) on the 2-dimensional differentiable manifold M - sage: XM.tensor((1,0), name='a') - Vector field a on the 2-dimensional differentiable manifold M - sage: XM.tensor((0,2), name='a', antisym=(0,1)) - 2-form a on the 2-dimensional differentiable manifold M .. SEEALSO:: @@ -824,6 +848,86 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, self, tensor_type, name=name, latex_name=latex_name, sym=sym, antisym=antisym) + tensor_power = FiniteRankFreeModule.tensor_power + + tensor_product = FiniteRankFreeModule.tensor_product + + def tensor(self, *args, **kwds): + r""" + Construct a tensor field on the domain of ``self`` or a tensor product of ``self`` with other modules. + + If ``args`` consist of other parents, just delegate to :meth:`tensor_product`. + + Otherwise, construct a tensor (i.e., a tensor field on the domain of + the vector field module) from the following input. + + INPUT: + + - ``tensor_type`` -- pair (k,l) with k being the contravariant rank + and l the covariant rank + - ``name`` -- (string; default: ``None``) name given to the tensor + - ``latex_name`` -- (string; default: ``None``) LaTeX symbol to denote + the tensor; if none is provided, the LaTeX symbol is set to ``name`` + - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries + among the tensor arguments: each symmetry is described by a tuple + containing the positions of the involved arguments, with the + convention position=0 for the first argument; for instance: + + * ``sym=(0,1)`` for a symmetry between the 1st and 2nd arguments + * ``sym=[(0,2),(1,3,4)]`` for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments + + - ``antisym`` -- (default: ``None``) antisymmetry or list of + antisymmetries among the arguments, with the same convention + as for ``sym`` + - ``specific_type`` -- (default: ``None``) specific subclass of + :class:`~sage.manifolds.differentiable.tensorfield.TensorField` for + the output + + OUTPUT: + + - instance of + :class:`~sage.manifolds.differentiable.tensorfield.TensorField` + representing the tensor defined on the vector field module with the + provided characteristics + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: XM = M.vector_field_module() + sage: XM.tensor((1,2), name='t') + Tensor field t of type (1,2) on the 2-dimensional differentiable + manifold M + sage: XM.tensor((1,0), name='a') + Vector field a on the 2-dimensional differentiable manifold M + sage: XM.tensor((0,2), name='a', antisym=(0,1)) + 2-form a on the 2-dimensional differentiable manifold M + + Delegation to :meth:`tensor_product`:: + + sage: M = Manifold(2, 'M') + sage: XM = M.vector_field_module() + sage: XM.tensor(XM) + Module T^(2,0)(M) of type-(2,0) tensors fields on the 2-dimensional differentiable manifold M + sage: XM.tensor(XM, XM.dual(), XM) + Module T^(3,1)(M) of type-(3,1) tensors fields on the 2-dimensional differentiable manifold M + sage: XM.tensor(XM).tensor(XM.dual().tensor(XM.dual())) + Traceback (most recent call last): + ... + AttributeError: 'TensorFieldModule_with_category' object has no attribute '_basis_sym' + + .. SEEALSO:: + + :class:`~sage.manifolds.differentiable.tensorfield.TensorField` + for more examples and documentation. + """ + # Until https://trac.sagemath.org/ticket/30373 is done, + # TensorProductFunctor._functor_name is "tensor", so this method + # also needs to double as the tensor product construction + if isinstance(args[0], Parent): + return self.tensor_product(*args, **kwds) + return self._tensor(*args, **kwds) + def alternating_contravariant_tensor(self, degree, name=None, latex_name=None): r""" From 676772c0dde6391c48cdc2e142d56f6c95479e54 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 26 Sep 2022 19:59:04 -0700 Subject: [PATCH 391/414] src/sage/manifolds/differentiable/vectorfield_module.py (VectorFieldModule_abstract): New --- .../differentiable/diff_form_module.py | 8 ++----- .../differentiable/tensorfield_module.py | 8 ++----- .../differentiable/vectorfield_module.py | 23 ++++++++++++++----- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/sage/manifolds/differentiable/diff_form_module.py b/src/sage/manifolds/differentiable/diff_form_module.py index 4b9a7cae78c..5a337b6c72c 100644 --- a/src/sage/manifolds/differentiable/diff_form_module.py +++ b/src/sage/manifolds/differentiable/diff_form_module.py @@ -47,10 +47,10 @@ from sage.manifolds.differentiable.diff_form import DiffForm, DiffFormParal from sage.manifolds.differentiable.tensorfield import TensorField from sage.manifolds.differentiable.tensorfield_paral import TensorFieldParal -from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule +from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule_abstract -class DiffFormModule(UniqueRepresentation, Parent): +class DiffFormModule(VectorFieldModule_abstract): r""" Module of differential forms of a given degree `p` (`p`-forms) along a differentiable manifold `U` with values on a differentiable manifold `M`. @@ -558,10 +558,6 @@ def tensor_type(self): return (0, 1) raise NotImplementedError - tensor_power = VectorFieldModule.tensor_power - - tensor = tensor_product = VectorFieldModule.tensor_product - def degree(self): r""" Return the degree of the differential forms in ``self``. diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index d845ae03766..0479792a82e 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -50,10 +50,10 @@ MultivectorFieldParal) from sage.manifolds.differentiable.automorphismfield import (AutomorphismField, AutomorphismFieldParal) -from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule +from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule_abstract -class TensorFieldModule(UniqueRepresentation, Parent): +class TensorFieldModule(VectorFieldModule_abstract): r""" Module of tensor fields of a given type `(k,l)` along a differentiable manifold `U` with values on a differentiable manifold `M`, via a @@ -563,10 +563,6 @@ def tensor_type(self): """ return self._tensor_type - tensor_power = VectorFieldModule.tensor_power - - tensor = tensor_product = VectorFieldModule.tensor_product - @cached_method def zero(self): """ diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 8c023117eab..1d850014fab 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -53,14 +53,29 @@ from sage.rings.integer import Integer from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation -from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule +from sage.tensor.modules.finite_rank_free_module import ( + FiniteRankFreeModule_abstract, + FiniteRankFreeModule, +) if TYPE_CHECKING: from sage.manifolds.differentiable.diff_map import DiffMap from sage.manifolds.differentiable.manifold import DifferentiableManifold -class VectorFieldModule(UniqueRepresentation, Parent): +class VectorFieldModule_abstract(UniqueRepresentation, Parent): + r""" + Abstract base class for modules of vector fields. + """ + + tensor_power = FiniteRankFreeModule_abstract.tensor_power + + tensor_product = FiniteRankFreeModule_abstract.tensor_product + + tensor = FiniteRankFreeModule_abstract.tensor + + +class VectorFieldModule(VectorFieldModule_abstract): r""" Module of vector fields along a differentiable manifold `U` with values on a differentiable manifold `M`, via a differentiable @@ -848,10 +863,6 @@ def _tensor(self, tensor_type, name=None, latex_name=None, sym=None, self, tensor_type, name=name, latex_name=latex_name, sym=sym, antisym=antisym) - tensor_power = FiniteRankFreeModule.tensor_power - - tensor_product = FiniteRankFreeModule.tensor_product - def tensor(self, *args, **kwds): r""" Construct a tensor field on the domain of ``self`` or a tensor product of ``self`` with other modules. From 610545e6e2f83b6200675fd708cfb47b280413cb Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Sep 2022 14:11:38 -0700 Subject: [PATCH 392/414] TensorFreeModule.tensor_factors: Go through methods tensor_type, base_module --- src/sage/tensor/modules/tensor_free_module.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index b04e8581148..823a9482383 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -382,12 +382,14 @@ def tensor_factors(self): Dual of the Rank-3 free module M over the Integer Ring, Dual of the Rank-3 free module M over the Integer Ring] """ - if self._tensor_type == (0,1): # case of the dual + tensor_type = self.tensor_type() + if tensor_type == (0,1): # case of the dual raise NotImplementedError - factors = [self._fmodule] * self._tensor_type[0] - dmodule = self._fmodule.dual() - if self._tensor_type[1]: - factors += [dmodule] * self._tensor_type[1] + bmodule = self.base_module() + factors = [bmodule] * tensor_type[0] + dmodule = bmodule.dual() + if tensor_type[1]: + factors += [dmodule] * tensor_type[1] return factors #### Parent Methods From a6ba1011bb5d8601ed0acf1dbc845297042ea1a1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Sep 2022 14:11:53 -0700 Subject: [PATCH 393/414] TensorFieldModule.tensor_factors: New --- src/sage/manifolds/differentiable/tensorfield_module.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index 0479792a82e..3c3aa7d71f2 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -228,6 +228,12 @@ class TensorFieldModule(VectorFieldModule_abstract): [1 0] [0 1] + TESTS:: + + sage: T11.tensor_factors() + [Module X(M) of vector fields on the 2-dimensional differentiable manifold M, + Module Omega^1(M) of 1-forms on the 2-dimensional differentiable manifold M] + """ Element = TensorField @@ -288,6 +294,8 @@ def __init__(self, vector_field_module, tensor_type): self._dest_map = dest_map self._ambient_domain = vector_field_module._ambient_domain + tensor_factors = TensorFreeModule.tensor_factors + #### Parent methods def _element_constructor_(self, comp=[], frame=None, name=None, From 8e0bf363b0ffab0336e4218fa986fac3bcef4b48 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Sep 2022 17:29:27 -0700 Subject: [PATCH 394/414] TensorFieldModule: Put it in the category Modules.TensorProducts --- src/sage/manifolds/differentiable/tensorfield.py | 4 ++-- src/sage/manifolds/differentiable/tensorfield_module.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index a2aad4d4937..db4a2a05aa1 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -176,8 +176,8 @@ class TensorField(ModuleElementWithMutability): Module T^(0,2)(S^2) of type-(0,2) tensors fields on the 2-dimensional differentiable manifold S^2 sage: t.parent().category() - Category of modules over Algebra of differentiable scalar fields on the - 2-dimensional differentiable manifold S^2 + Category of tensor products of modules over Algebra of differentiable scalar fields + on the 2-dimensional differentiable manifold S^2 The parent of `t` is not a free module, for the sphere `S^2` is not parallelizable:: diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index 3c3aa7d71f2..4bb33109c25 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -125,8 +125,8 @@ class TensorFieldModule(VectorFieldModule_abstract): `T^{(2,0)}(M)` is a module over the algebra `C^k(M)`:: sage: T20.category() - Category of modules over Algebra of differentiable scalar fields on the - 2-dimensional differentiable manifold M + Category of tensor products of modules over Algebra of differentiable scalar fields + on the 2-dimensional differentiable manifold M sage: T20.base_ring() is M.scalar_field_algebra() True @@ -237,7 +237,7 @@ class TensorFieldModule(VectorFieldModule_abstract): """ Element = TensorField - def __init__(self, vector_field_module, tensor_type): + def __init__(self, vector_field_module, tensor_type, category=None): r""" Construct a module of tensor fields taking values on a (a priori) not parallelizable differentiable manifold. @@ -289,7 +289,8 @@ def __init__(self, vector_field_module, tensor_type): # the member self._ring is created for efficiency (to avoid calls to # self.base_ring()): self._ring = domain.scalar_field_algebra() - Parent.__init__(self, base=self._ring, category=Modules(self._ring)) + category = Modules(self._ring).TensorProducts().or_subcategory(category) + Parent.__init__(self, base=self._ring, category=category) self._domain = domain self._dest_map = dest_map self._ambient_domain = vector_field_module._ambient_domain From 20bd0d3f7ced5dc76e1b9032903254cf9aeb6236 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 27 Sep 2022 17:50:31 -0700 Subject: [PATCH 395/414] src/sage/manifolds/differentiable/tensorfield_module.py: Update copyright according to git blame -w --date=format:%Y FILE | sort -k2 --- src/sage/manifolds/differentiable/tensorfield_module.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index 4bb33109c25..dd9634f066b 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -27,9 +27,11 @@ """ # ***************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon -# Copyright (C) 2015 Michal Bejger -# Copyright (C) 2016 Travis Scrimshaw +# Copyright (C) 2015-2018 Eric Gourgoulhon +# 2015 Michal Bejger +# 2016 Travis Scrimshaw +# 2020 Michael Jung +# 2022 Matthias Koeppe # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of From 38e6b3e414c15dde36ba3e7957539e7727d8a14d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Sep 2022 16:53:19 -0700 Subject: [PATCH 396/414] sage.tensor.modules.reflexive_module: New, refactor finite rank free modules and vector field modules through the new classes --- .../differentiable/tensorfield_module.py | 7 +- .../differentiable/vectorfield_module.py | 47 +-- .../tensor/modules/finite_rank_free_module.py | 193 +----------- src/sage/tensor/modules/reflexive_module.py | 276 ++++++++++++++++++ src/sage/tensor/modules/tensor_free_module.py | 29 +- 5 files changed, 298 insertions(+), 254 deletions(-) create mode 100644 src/sage/tensor/modules/reflexive_module.py diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index dd9634f066b..75344239d0c 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -43,6 +43,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.categories.modules import Modules +from sage.tensor.modules.reflexive_module import ReflexiveModule_tensor from sage.tensor.modules.tensor_free_module import TensorFreeModule from sage.manifolds.differentiable.tensorfield import TensorField from sage.manifolds.differentiable.tensorfield_paral import TensorFieldParal @@ -55,7 +56,7 @@ from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule_abstract -class TensorFieldModule(VectorFieldModule_abstract): +class TensorFieldModule(ReflexiveModule_tensor, VectorFieldModule_abstract): r""" Module of tensor fields of a given type `(k,l)` along a differentiable manifold `U` with values on a differentiable manifold `M`, via a @@ -297,8 +298,6 @@ def __init__(self, vector_field_module, tensor_type, category=None): self._dest_map = dest_map self._ambient_domain = vector_field_module._ambient_domain - tensor_factors = TensorFreeModule.tensor_factors - #### Parent methods def _element_constructor_(self, comp=[], frame=None, name=None, @@ -601,7 +600,7 @@ def zero(self): #*********************************************************************** -class TensorFieldFreeModule(TensorFreeModule): +class TensorFieldFreeModule(TensorFreeModule, VectorFieldModule_abstract): r""" Free module of tensor fields of a given type `(k,l)` along a differentiable manifold `U` with values on a parallelizable manifold `M`, diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 1d850014fab..3effe04cbb7 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -53,9 +53,10 @@ from sage.rings.integer import Integer from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation -from sage.tensor.modules.finite_rank_free_module import ( - FiniteRankFreeModule_abstract, - FiniteRankFreeModule, +from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule +from sage.tensor.modules.reflexive_module import ( + ReflexiveModule_abstract, + ReflexiveModule_base ) if TYPE_CHECKING: @@ -63,19 +64,13 @@ from sage.manifolds.differentiable.manifold import DifferentiableManifold -class VectorFieldModule_abstract(UniqueRepresentation, Parent): +class VectorFieldModule_abstract(UniqueRepresentation, ReflexiveModule_abstract): r""" Abstract base class for modules of vector fields. """ - tensor_power = FiniteRankFreeModule_abstract.tensor_power - tensor_product = FiniteRankFreeModule_abstract.tensor_product - - tensor = FiniteRankFreeModule_abstract.tensor - - -class VectorFieldModule(VectorFieldModule_abstract): +class VectorFieldModule(ReflexiveModule_base, VectorFieldModule_abstract): r""" Module of vector fields along a differentiable manifold `U` with values on a differentiable manifold `M`, via a differentiable @@ -521,34 +516,6 @@ def destination_map(self): """ return self._dest_map - def base_module(self): - r""" - Return the module on which ``self`` is constructed, namely ``self`` itself. - - EXAMPLES:: - - sage: M = Manifold(2, 'M') - sage: XM = M.vector_field_module() - sage: XM.base_module() is XM - True - - """ - return self - - def tensor_type(self): - r""" - Return the tensor type of ``self``, the pair `(1, 0)`. - - EXAMPLES:: - - sage: M = Manifold(2, 'M') - sage: XM = M.vector_field_module() - sage: XM.tensor_type() - (1, 0) - - """ - return (1, 0) - def tensor_module(self, k, l, *, sym=None, antisym=None): r""" Return the module of type-`(k,l)` tensors on ``self``. @@ -1305,7 +1272,7 @@ def poisson_tensor( #****************************************************************************** -class VectorFieldFreeModule(FiniteRankFreeModule): +class VectorFieldFreeModule(FiniteRankFreeModule, VectorFieldModule_abstract): r""" Free module of vector fields along a differentiable manifold `U` with values on a parallelizable manifold `M`, via a differentiable map diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 61a44d022d4..534c99d0b3f 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -548,8 +548,14 @@ class :class:`~sage.modules.free_module.FreeModule_generic` from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm from sage.tensor.modules.free_module_element import FiniteRankFreeModuleElement from sage.tensor.modules.free_module_tensor import FreeModuleTensor +from sage.tensor.modules.reflexive_module import ( + ReflexiveModule_abstract, + ReflexiveModule_base, + ReflexiveModule_dual, +) -class FiniteRankFreeModule_abstract(UniqueRepresentation, Parent): + +class FiniteRankFreeModule_abstract(UniqueRepresentation, ReflexiveModule_abstract): r""" Abstract base class for free modules of finite rank over a commutative ring. """ @@ -619,130 +625,6 @@ def _latex_(self): else: return self._latex_name - def tensor_power(self, n): - r""" - Return the ``n``-fold tensor product of ``self``. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(QQ, 2) - sage: M.tensor_power(3) - Free module of type-(3,0) tensors on the 2-dimensional vector space over the Rational Field - sage: M.tensor_module(1,2).tensor_power(3) - Free module of type-(3,6) tensors on the 2-dimensional vector space over the Rational Field - """ - tensor_type = self.tensor_type() - return self.base_module().tensor_module(n * tensor_type[0], n * tensor_type[1]) - - def tensor_product(self, *others): - r""" - Return the tensor product of ``self`` and ``others``. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(QQ, 2) - sage: M.tensor_product(M) - Free module of type-(2,0) tensors on the 2-dimensional vector space over the Rational Field - sage: M.tensor_product(M.dual()) - Free module of type-(1,1) tensors on the 2-dimensional vector space over the Rational Field - sage: M.dual().tensor_product(M, M.dual()) - Free module of type-(1,2) tensors on the 2-dimensional vector space over the Rational Field - sage: M.tensor_product(M.tensor_module(1,2)) - Free module of type-(2,2) tensors on the 2-dimensional vector space over the Rational Field - sage: M.tensor_module(1,2).tensor_product(M) - Free module of type-(2,2) tensors on the 2-dimensional vector space over the Rational Field - sage: M.tensor_module(1,1).tensor_product(M.tensor_module(1,2)) - Free module of type-(2,3) tensors on the 2-dimensional vector space over the Rational Field - - sage: Sym2M = M.tensor_module(2, 0, sym=range(2)); Sym2M - Free module of fully symmetric type-(2,0) tensors on the 2-dimensional vector space over the Rational Field - sage: Sym01x23M = Sym2M.tensor_product(Sym2M); Sym01x23M - Free module of type-(4,0) tensors on the 2-dimensional vector space over the Rational Field, - with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) - sage: Sym01x23M._index_maps - ((0, 1), (2, 3)) - - sage: N = M.tensor_module(3, 3, sym=[1, 2], antisym=[3, 4]); N - Free module of type-(3,3) tensors on the 2-dimensional vector space over the Rational Field, - with symmetry on the index positions (1, 2), - with antisymmetry on the index positions (3, 4) - sage: NxN = N.tensor_product(N); NxN - Free module of type-(6,6) tensors on the 2-dimensional vector space over the Rational Field, - with symmetry on the index positions (1, 2), with symmetry on the index positions (4, 5), - with antisymmetry on the index positions (6, 7), with antisymmetry on the index positions (9, 10) - sage: NxN._index_maps - ((0, 1, 2, 6, 7, 8), (3, 4, 5, 9, 10, 11)) - """ - from sage.modules.free_module_element import vector - from .comp import CompFullySym, CompFullyAntiSym, CompWithSym - - base_module = self.base_module() - if not all(module.base_module() == base_module for module in others): - raise NotImplementedError('all factors must be tensor modules over the same base module') - factors = [self] + list(others) - result_tensor_type = sum(vector(factor.tensor_type()) for factor in factors) - result_sym = [] - result_antisym = [] - # Keep track of reordering of the contravariant and covariant indices - # (compatible with FreeModuleTensor.__mul__) - index_maps = [] - running_indices = vector([0, result_tensor_type[0]]) - for factor in factors: - tensor_type = factor.tensor_type() - index_map = tuple(i + running_indices[0] for i in range(tensor_type[0])) - index_map += tuple(i + running_indices[1] for i in range(tensor_type[1])) - index_maps.append(index_map) - - if tensor_type[0] + tensor_type[1] > 1: - basis_sym = factor._basis_sym() - all_indices = tuple(range(tensor_type[0] + tensor_type[1])) - if isinstance(basis_sym, CompFullySym): - sym = [all_indices] - antisym = [] - elif isinstance(basis_sym, CompFullyAntiSym): - sym = [] - antisym = [all_indices] - elif isinstance(basis_sym, CompWithSym): - sym = basis_sym._sym - antisym = basis_sym._antisym - else: - sym = antisym = [] - - def map_isym(isym): - return tuple(index_map[i] for i in isym) - - result_sym.extend(tuple(index_map[i] for i in isym) for isym in sym) - result_antisym.extend(tuple(index_map[i] for i in isym) for isym in antisym) - - running_indices += vector(tensor_type) - - result = base_module.tensor_module(*result_tensor_type, - sym=result_sym, antisym=result_antisym) - result._index_maps = tuple(index_maps) - return result - - def tensor(self, *args, **kwds): - # Until https://trac.sagemath.org/ticket/30373 is done, - # TensorProductFunctor._functor_name is "tensor", so here we delegate. - r""" - Return the tensor product of ``self`` and ``others``. - - This method is invoked when :class:`~sage.categories.tensor.TensorProductFunctor` - is applied to parents. - - It just delegates to :meth:`tensor_product`. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(QQ, 2); M - 2-dimensional vector space over the Rational Field - sage: M20 = M.tensor_module(2, 0); M20 - Free module of type-(2,0) tensors on the 2-dimensional vector space over the Rational Field - sage: tensor([M20, M20]) - Free module of type-(4,0) tensors on the 2-dimensional vector space over the Rational Field - """ - return self.tensor_product(*args, **kwds) - def rank(self) -> int: r""" Return the rank of the free module ``self``. @@ -1092,7 +974,7 @@ def _test_isomorphism_with_fixed_basis(self, **options): tester.assertEqual(morphism.codomain().rank(), self.rank()) -class FiniteRankFreeModule(FiniteRankFreeModule_abstract): +class FiniteRankFreeModule(ReflexiveModule_base, FiniteRankFreeModule_abstract): r""" Free module of finite rank over a commutative ring. @@ -3430,34 +3312,8 @@ def identity_map(self, name='Id', latex_name=None): self._identity_map.set_name(name=name, latex_name=latex_name) return self._identity_map - def base_module(self): - r""" - Return the free module on which ``self`` is constructed, namely ``self`` itself. - - EXAMPLES:: - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M.base_module() is M - True - - """ - return self - - def tensor_type(self): - r""" - Return the tensor type of ``self``, the pair `(1, 0)`. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3) - sage: M.tensor_type() - (1, 0) - - """ - return (1, 0) - - -class FiniteRankDualFreeModule(FiniteRankFreeModule_abstract): +class FiniteRankDualFreeModule(ReflexiveModule_dual, FiniteRankFreeModule_abstract): r""" Dual of a free module of finite rank over a commutative ring. @@ -3584,24 +3440,6 @@ def __init__(self, fmodule, name=None, latex_name=None): latex_name=latex_name) fmodule._all_modules.add(self) - def construction(self): - r""" - Return the functorial construction of ``self``. - - This implementation just returns ``None``, as no functorial construction is implemented. - - TESTS:: - - sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerDualFreeModule - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: A = M.dual() - sage: A.construction() is None - True - """ - # No construction until we extend VectorFunctor with a parameter 'dual' - return None - #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, @@ -3736,16 +3574,3 @@ def base_module(self): """ return self._fmodule - - def tensor_type(self): - r""" - Return the tensor type of ``self``. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M.dual().tensor_type() - (0, 1) - - """ - return (0, 1) diff --git a/src/sage/tensor/modules/reflexive_module.py b/src/sage/tensor/modules/reflexive_module.py new file mode 100644 index 00000000000..84fbc673017 --- /dev/null +++ b/src/sage/tensor/modules/reflexive_module.py @@ -0,0 +1,276 @@ +r""" +Base class for reflexive modules +""" + +from sage.misc.abstract_method import abstract_method +from sage.structure.parent import Parent + + +class ReflexiveModule_abstract(Parent): + r""" + Abstract base class for reflexive modules. + """ + + @abstract_method(optional=True) + def tensor_type(self): + r""" + Return the tensor type of ``self``. + + OUTPUT: + + - pair `(k,l)` such that ``self`` is the module tensor product + `T^{(k,l)}(M)`, where `M` is the :meth:`base_module` of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: T = M.tensor_module(1, 2) + sage: T.tensor_type() + (1, 2) + """ + + @abstract_method + def base_module(self): + r""" + Return the module on which ``self`` is constructed. + """ + + def dual(self): + r""" + Return the dual module. + """ + k, l = self.tensor_type() + return self.base_module().tensor_module(l, k) + + def tensor(self, *args, **kwds): + # Until https://trac.sagemath.org/ticket/30373 is done, + # TensorProductFunctor._functor_name is "tensor", so here we delegate. + r""" + Return the tensor product of ``self`` and ``others``. + + This method is invoked when :class:`~sage.categories.tensor.TensorProductFunctor` + is applied to parents. + + It just delegates to :meth:`tensor_product`. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2); M + 2-dimensional vector space over the Rational Field + sage: M20 = M.tensor_module(2, 0); M20 + Free module of type-(2,0) tensors on the 2-dimensional vector space over the Rational Field + sage: tensor([M20, M20]) + Free module of type-(4,0) tensors on the 2-dimensional vector space over the Rational Field + """ + return self.tensor_product(*args, **kwds) + + def tensor_power(self, n): + r""" + Return the ``n``-fold tensor product of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2) + sage: M.tensor_power(3) + Free module of type-(3,0) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_module(1,2).tensor_power(3) + Free module of type-(3,6) tensors on the 2-dimensional vector space over the Rational Field + """ + tensor_type = self.tensor_type() + return self.base_module().tensor_module(n * tensor_type[0], n * tensor_type[1]) + + def tensor_product(self, *others): + r""" + Return the tensor product of ``self`` and ``others``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2) + sage: M.tensor_product(M) + Free module of type-(2,0) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_product(M.dual()) + Free module of type-(1,1) tensors on the 2-dimensional vector space over the Rational Field + sage: M.dual().tensor_product(M, M.dual()) + Free module of type-(1,2) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_product(M.tensor_module(1,2)) + Free module of type-(2,2) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_module(1,2).tensor_product(M) + Free module of type-(2,2) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_module(1,1).tensor_product(M.tensor_module(1,2)) + Free module of type-(2,3) tensors on the 2-dimensional vector space over the Rational Field + + sage: Sym2M = M.tensor_module(2, 0, sym=range(2)); Sym2M + Free module of fully symmetric type-(2,0) tensors on the 2-dimensional vector space over the Rational Field + sage: Sym01x23M = Sym2M.tensor_product(Sym2M); Sym01x23M + Free module of type-(4,0) tensors on the 2-dimensional vector space over the Rational Field, + with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) + sage: Sym01x23M._index_maps + ((0, 1), (2, 3)) + + sage: N = M.tensor_module(3, 3, sym=[1, 2], antisym=[3, 4]); N + Free module of type-(3,3) tensors on the 2-dimensional vector space over the Rational Field, + with symmetry on the index positions (1, 2), + with antisymmetry on the index positions (3, 4) + sage: NxN = N.tensor_product(N); NxN + Free module of type-(6,6) tensors on the 2-dimensional vector space over the Rational Field, + with symmetry on the index positions (1, 2), with symmetry on the index positions (4, 5), + with antisymmetry on the index positions (6, 7), with antisymmetry on the index positions (9, 10) + sage: NxN._index_maps + ((0, 1, 2, 6, 7, 8), (3, 4, 5, 9, 10, 11)) + """ + from sage.modules.free_module_element import vector + from .comp import CompFullySym, CompFullyAntiSym, CompWithSym + + base_module = self.base_module() + if not all(module.base_module() == base_module for module in others): + raise NotImplementedError('all factors must be tensor modules over the same base module') + factors = [self] + list(others) + result_tensor_type = sum(vector(factor.tensor_type()) for factor in factors) + result_sym = [] + result_antisym = [] + # Keep track of reordering of the contravariant and covariant indices + # (compatible with FreeModuleTensor.__mul__) + index_maps = [] + running_indices = vector([0, result_tensor_type[0]]) + for factor in factors: + tensor_type = factor.tensor_type() + index_map = tuple(i + running_indices[0] for i in range(tensor_type[0])) + index_map += tuple(i + running_indices[1] for i in range(tensor_type[1])) + index_maps.append(index_map) + + if tensor_type[0] + tensor_type[1] > 1: + basis_sym = factor._basis_sym() + all_indices = tuple(range(tensor_type[0] + tensor_type[1])) + if isinstance(basis_sym, CompFullySym): + sym = [all_indices] + antisym = [] + elif isinstance(basis_sym, CompFullyAntiSym): + sym = [] + antisym = [all_indices] + elif isinstance(basis_sym, CompWithSym): + sym = basis_sym._sym + antisym = basis_sym._antisym + else: + sym = antisym = [] + + def map_isym(isym): + return tuple(index_map[i] for i in isym) + + result_sym.extend(tuple(index_map[i] for i in isym) for isym in sym) + result_antisym.extend(tuple(index_map[i] for i in isym) for isym in antisym) + + running_indices += vector(tensor_type) + + result = base_module.tensor_module(*result_tensor_type, + sym=result_sym, antisym=result_antisym) + result._index_maps = tuple(index_maps) + return result + + +class ReflexiveModule_base: + + def base_module(self): + r""" + Return the free module on which ``self`` is constructed, namely ``self`` itself. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.base_module() is M + True + + sage: M = Manifold(2, 'M') + sage: XM = M.vector_field_module() + sage: XM.base_module() is XM + True + """ + return self + + def tensor_type(self): + r""" + Return the tensor type of ``self``, the pair `(1, 0)`. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.tensor_type() + (1, 0) + + sage: M = Manifold(2, 'M') + sage: XM = M.vector_field_module() + sage: XM.tensor_type() + (1, 0) + """ + return (1, 0) + + def dual(self): + r""" + Return the dual module. + """ + return self.tensor_module(0, 1) + + @abstract_method + def tensor_module(self, k, l, **kwds): + r""" + Return the module of all tensors of type `(k, l)` defined on ``self``. + """ + + +class ReflexiveModule_dual: + + def tensor_type(self): + r""" + Return the tensor type of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.dual().tensor_type() + (0, 1) + + """ + return (0, 1) + + def construction(self): + r""" + Return the functorial construction of ``self``. + + This implementation just returns ``None``, as no functorial construction is implemented. + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = M.dual() + sage: A.construction() is None + True + """ + return None + + +class ReflexiveModule_tensor: + + def tensor_factors(self): + r""" + Return the tensor factors of this tensor module. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T = M.tensor_module(2, 3) + sage: T.tensor_factors() + [Rank-3 free module M over the Integer Ring, + Rank-3 free module M over the Integer Ring, + Dual of the Rank-3 free module M over the Integer Ring, + Dual of the Rank-3 free module M over the Integer Ring, + Dual of the Rank-3 free module M over the Integer Ring] + """ + tensor_type = self.tensor_type() + if tensor_type == (0,1): # case of the dual + raise NotImplementedError + bmodule = self.base_module() + factors = [bmodule] * tensor_type[0] + dmodule = bmodule.dual() + if tensor_type[1]: + factors += [dmodule] * tensor_type[1] + return factors diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index 823a9482383..ecaf4c46886 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -67,9 +67,11 @@ from sage.tensor.modules.free_module_morphism import \ FiniteRankFreeModuleMorphism from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism +from sage.tensor.modules.reflexive_module import ReflexiveModule_tensor + from .tensor_free_submodule_basis import TensorFreeSubmoduleBasis_sym -class TensorFreeModule(FiniteRankFreeModule_abstract): +class TensorFreeModule(FiniteRankFreeModule_abstract, ReflexiveModule_tensor): r""" Class for the free modules over a commutative ring `R` that are tensor products of a given free module `M` over `R` with itself and its @@ -367,31 +369,6 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, category=No super().__init__(fmodule._ring, rank, name=name, latex_name=latex_name, category=category) fmodule._all_modules.add(self) - def tensor_factors(self): - r""" - Return the tensor factors of this tensor module. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: T = M.tensor_module(2, 3) - sage: T.tensor_factors() - [Rank-3 free module M over the Integer Ring, - Rank-3 free module M over the Integer Ring, - Dual of the Rank-3 free module M over the Integer Ring, - Dual of the Rank-3 free module M over the Integer Ring, - Dual of the Rank-3 free module M over the Integer Ring] - """ - tensor_type = self.tensor_type() - if tensor_type == (0,1): # case of the dual - raise NotImplementedError - bmodule = self.base_module() - factors = [bmodule] * tensor_type[0] - dmodule = bmodule.dual() - if tensor_type[1]: - factors += [dmodule] * tensor_type[1] - return factors - #### Parent Methods def _element_constructor_(self, comp=[], basis=None, name=None, From 04a4daa81badfde762256b27734b423ca3763449 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 29 Sep 2022 16:58:50 -0700 Subject: [PATCH 397/414] src/sage/tensor/modules/reflexive_module.py: Make all classes subclasses of ..._abstract --- src/sage/tensor/modules/reflexive_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/tensor/modules/reflexive_module.py b/src/sage/tensor/modules/reflexive_module.py index 84fbc673017..92725c2d97a 100644 --- a/src/sage/tensor/modules/reflexive_module.py +++ b/src/sage/tensor/modules/reflexive_module.py @@ -167,7 +167,7 @@ def map_isym(isym): return result -class ReflexiveModule_base: +class ReflexiveModule_base(ReflexiveModule_abstract): def base_module(self): r""" @@ -216,7 +216,7 @@ def tensor_module(self, k, l, **kwds): """ -class ReflexiveModule_dual: +class ReflexiveModule_dual(ReflexiveModule_abstract): def tensor_type(self): r""" @@ -248,7 +248,7 @@ def construction(self): return None -class ReflexiveModule_tensor: +class ReflexiveModule_tensor(ReflexiveModule_abstract): def tensor_factors(self): r""" From 53e944a6a330ab81ea77fceab7d05d6552545a08 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 1 Oct 2022 22:59:53 -0700 Subject: [PATCH 398/414] src/sage/tensor/modules/reflexive_module.py: Add documentation --- src/sage/tensor/modules/reflexive_module.py | 113 +++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/src/sage/tensor/modules/reflexive_module.py b/src/sage/tensor/modules/reflexive_module.py index 92725c2d97a..06a4b58d28f 100644 --- a/src/sage/tensor/modules/reflexive_module.py +++ b/src/sage/tensor/modules/reflexive_module.py @@ -1,5 +1,5 @@ r""" -Base class for reflexive modules +Base classes for reflexive modules """ from sage.misc.abstract_method import abstract_method @@ -9,6 +9,51 @@ class ReflexiveModule_abstract(Parent): r""" Abstract base class for reflexive modules. + + An `R`-module `M` is *reflexive* if the natural map from `M` to its double + dual `M^{**}` is an isomorphism. + + In the category of `R`-modules, the dual module `M^*` is + the `R`-module of linear functionals $\phi:\ M \longrightarrow R$. + However, we do not make the assumption that the dual module + (obtained by :meth:`dual`) is in the category :class:`Homsets`. + + We identify the double dual `M^{**}` with `M`. + + Tensor products of reflexive modules are reflexive. We identify all + tensor products of `k` copies of `M` and `l` copies of `M^*` and + denote it by `T^{(k,l)}(M)`. The :meth:`tensor_type` of such a tensor + product is the pair `(k, l)`, and `M` is called its :meth:`base_module`. + + There are three abstract subclasses: + + - :class:`ReflexiveModule_base` is the base class for implementations + of base modules `M`. + + - :class:`ReflexiveModule_dual` is the base class for implementations + of duals `M^*`. + + - :class:`ReflexiveModule_tensor` is the base class for implementations + of tensor modules `T^{(k,l)}(M)`. + + TESTS:: + + sage: from sage.tensor.modules.reflexive_module import ( + ....: ReflexiveModule_abstract, ReflexiveModule_base, + ....: ReflexiveModule_dual, ReflexiveModule_tensor) + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: isinstance(M, ReflexiveModule_abstract) + True + sage: isinstance(M, ReflexiveModule_base) + True + sage: isinstance(M.dual(), ReflexiveModule_abstract) + True + sage: isinstance(M.dual(), ReflexiveModule_dual) + True + sage: isinstance(M.tensor_module(1, 1), ReflexiveModule_abstract) + True + sage: isinstance(M.tensor_module(1, 1), ReflexiveModule_tensor) + True """ @abstract_method(optional=True) @@ -33,11 +78,33 @@ def tensor_type(self): def base_module(self): r""" Return the module on which ``self`` is constructed. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.base_module() is M + True + sage: M.dual().base_module() is M + True + sage: M.tensor_module(1, 2).base_module() is M + True """ def dual(self): r""" Return the dual module. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.dual() + Dual of the Rank-3 free module over the Integer Ring + sage: M.dual().dual() + Rank-3 free module over the Integer Ring + sage: M.tensor_module(1, 2) + Free module of type-(1,2) tensors on the Rank-3 free module over the Integer Ring + sage: M.tensor_module(1, 2).dual() + Free module of type-(2,1) tensors on the Rank-3 free module over the Integer Ring """ k, l = self.tensor_type() return self.base_module().tensor_module(l, k) @@ -168,6 +235,16 @@ def map_isym(isym): class ReflexiveModule_base(ReflexiveModule_abstract): + r""" + Abstract base class for reflexive modules that are base modules. + + TESTS:: + + sage: from sage.tensor.modules.reflexive_module import ReflexiveModule_base + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: isinstance(M, ReflexiveModule_base) + True + """ def base_module(self): r""" @@ -206,6 +283,12 @@ def tensor_type(self): def dual(self): r""" Return the dual module. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.dual() + Dual of the Rank-3 free module M over the Integer Ring """ return self.tensor_module(0, 1) @@ -213,10 +296,26 @@ def dual(self): def tensor_module(self, k, l, **kwds): r""" Return the module of all tensors of type `(k, l)` defined on ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.tensor_module(1, 2) + Free module of type-(1,2) tensors on the Rank-3 free module over the Integer Ring """ class ReflexiveModule_dual(ReflexiveModule_abstract): + r""" + Abstract base class for reflexive modules that are the duals of base modules. + + TESTS:: + + sage: from sage.tensor.modules.reflexive_module import ReflexiveModule_dual + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: isinstance(M.dual(), ReflexiveModule_dual) + True + """ def tensor_type(self): r""" @@ -227,7 +326,6 @@ def tensor_type(self): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.dual().tensor_type() (0, 1) - """ return (0, 1) @@ -245,10 +343,21 @@ def construction(self): sage: A.construction() is None True """ + # Until https://trac.sagemath.org/ticket/34605 is done return None class ReflexiveModule_tensor(ReflexiveModule_abstract): + r""" + Abstract base class for reflexive modules that are tensor products of base modules. + + TESTS:: + + sage: from sage.tensor.modules.reflexive_module import ReflexiveModule_tensor + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: isinstance(M.tensor_module(1, 1), ReflexiveModule_tensor) + True + """ def tensor_factors(self): r""" From f107a775fd44066f3abc931c01257f3b13a53f2b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 3 Oct 2022 04:31:25 -0700 Subject: [PATCH 399/414] src/doc/en/reference/tensor_free_modules/tensors.rst: Add reflexive_module --- src/doc/en/reference/tensor_free_modules/tensors.rst | 2 ++ src/sage/tensor/modules/reflexive_module.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/doc/en/reference/tensor_free_modules/tensors.rst b/src/doc/en/reference/tensor_free_modules/tensors.rst index 434ea734191..65b7786a1cb 100644 --- a/src/doc/en/reference/tensor_free_modules/tensors.rst +++ b/src/doc/en/reference/tensor_free_modules/tensors.rst @@ -4,6 +4,8 @@ Tensors .. toctree:: :maxdepth: 2 + sage/tensor/modules/reflexive_module + sage/tensor/modules/tensor_free_module sage/tensor/modules/tensor_free_submodule diff --git a/src/sage/tensor/modules/reflexive_module.py b/src/sage/tensor/modules/reflexive_module.py index 06a4b58d28f..f969d547811 100644 --- a/src/sage/tensor/modules/reflexive_module.py +++ b/src/sage/tensor/modules/reflexive_module.py @@ -14,7 +14,7 @@ class ReflexiveModule_abstract(Parent): dual `M^{**}` is an isomorphism. In the category of `R`-modules, the dual module `M^*` is - the `R`-module of linear functionals $\phi:\ M \longrightarrow R$. + the `R`-module of linear functionals `\phi:\ M \longrightarrow R`. However, we do not make the assumption that the dual module (obtained by :meth:`dual`) is in the category :class:`Homsets`. From d2228bd2e747c68fecb69617b49d4cad988682e1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Oct 2022 11:05:10 -0700 Subject: [PATCH 400/414] src/sage/tensor/modules/tensor_free_module.py (TensorFreeModule): Make MRO consistent with FiniteRankFreeModule --- src/sage/tensor/modules/tensor_free_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index ecaf4c46886..3dd3a573c89 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -71,7 +71,7 @@ from .tensor_free_submodule_basis import TensorFreeSubmoduleBasis_sym -class TensorFreeModule(FiniteRankFreeModule_abstract, ReflexiveModule_tensor): +class TensorFreeModule(ReflexiveModule_tensor, FiniteRankFreeModule_abstract): r""" Class for the free modules over a commutative ring `R` that are tensor products of a given free module `M` over `R` with itself and its From 8a2866bf17cf8119b719741c0b9e0574fee58a04 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 4 Oct 2022 14:35:13 -0700 Subject: [PATCH 401/414] get rid of VectorFieldModule_abstract --- src/sage/manifolds/differentiable/diff_form_module.py | 6 ++++-- .../manifolds/differentiable/tensorfield_module.py | 5 ++--- .../manifolds/differentiable/vectorfield_module.py | 10 ++-------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/sage/manifolds/differentiable/diff_form_module.py b/src/sage/manifolds/differentiable/diff_form_module.py index 5a337b6c72c..04d85a0817d 100644 --- a/src/sage/manifolds/differentiable/diff_form_module.py +++ b/src/sage/manifolds/differentiable/diff_form_module.py @@ -47,10 +47,10 @@ from sage.manifolds.differentiable.diff_form import DiffForm, DiffFormParal from sage.manifolds.differentiable.tensorfield import TensorField from sage.manifolds.differentiable.tensorfield_paral import TensorFieldParal -from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule_abstract +from sage.tensor.modules.reflexive_module import ReflexiveModule_abstract -class DiffFormModule(VectorFieldModule_abstract): +class DiffFormModule(UniqueRepresentation, Parent): r""" Module of differential forms of a given degree `p` (`p`-forms) along a differentiable manifold `U` with values on a differentiable manifold `M`. @@ -535,6 +535,8 @@ def base_module(self): """ return self._vmodule + tensor = tensor_product = ReflexiveModule_abstract.tensor_product + def tensor_type(self): r""" Return the tensor type of ``self`` if ``self`` is a module of 1-forms. diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index 75344239d0c..6347189848f 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -53,10 +53,9 @@ MultivectorFieldParal) from sage.manifolds.differentiable.automorphismfield import (AutomorphismField, AutomorphismFieldParal) -from sage.manifolds.differentiable.vectorfield_module import VectorFieldModule_abstract -class TensorFieldModule(ReflexiveModule_tensor, VectorFieldModule_abstract): +class TensorFieldModule(UniqueRepresentation, ReflexiveModule_tensor): r""" Module of tensor fields of a given type `(k,l)` along a differentiable manifold `U` with values on a differentiable manifold `M`, via a @@ -600,7 +599,7 @@ def zero(self): #*********************************************************************** -class TensorFieldFreeModule(TensorFreeModule, VectorFieldModule_abstract): +class TensorFieldFreeModule(TensorFreeModule): r""" Free module of tensor fields of a given type `(k,l)` along a differentiable manifold `U` with values on a parallelizable manifold `M`, diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 3effe04cbb7..8cd62dacc8c 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -64,13 +64,7 @@ from sage.manifolds.differentiable.manifold import DifferentiableManifold -class VectorFieldModule_abstract(UniqueRepresentation, ReflexiveModule_abstract): - r""" - Abstract base class for modules of vector fields. - """ - - -class VectorFieldModule(ReflexiveModule_base, VectorFieldModule_abstract): +class VectorFieldModule(UniqueRepresentation, ReflexiveModule_base): r""" Module of vector fields along a differentiable manifold `U` with values on a differentiable manifold `M`, via a differentiable @@ -1272,7 +1266,7 @@ def poisson_tensor( #****************************************************************************** -class VectorFieldFreeModule(FiniteRankFreeModule, VectorFieldModule_abstract): +class VectorFieldFreeModule(FiniteRankFreeModule): r""" Free module of vector fields along a differentiable manifold `U` with values on a parallelizable manifold `M`, via a differentiable map From 9861b026a44c57743775b32cb741a969e13d8786 Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Wed, 26 Oct 2022 20:27:23 -0700 Subject: [PATCH 402/414] trac 34652: import NN directly rather than lazily --- src/sage/categories/sets_cat.py | 6 +++--- src/sage/combinat/cluster_complex.py | 2 +- src/sage/combinat/crystals/tensor_product.py | 2 +- src/sage/combinat/integer_lists/nn.py | 2 +- src/sage/combinat/integer_vector.py | 2 +- src/sage/combinat/integer_vectors_mod_permgroup.py | 2 +- src/sage/combinat/interval_posets.py | 2 +- src/sage/combinat/non_decreasing_parking_function.py | 2 +- src/sage/combinat/partition.py | 2 +- src/sage/combinat/partition_kleshchev.py | 2 +- src/sage/combinat/partition_tuple.py | 2 +- src/sage/combinat/path_tableaux/semistandard.py | 2 +- src/sage/combinat/posets/poset_examples.py | 4 ++-- src/sage/combinat/root_system/cartan_type.py | 2 +- src/sage/combinat/root_system/root_lattice_realizations.py | 2 +- src/sage/combinat/root_system/type_super_A.py | 2 +- src/sage/combinat/sine_gordon.py | 2 +- src/sage/combinat/symmetric_group_algebra.py | 2 +- src/sage/combinat/tableau_tuple.py | 2 +- src/sage/combinat/words/abstract_word.py | 2 +- src/sage/rings/semirings/all.py | 6 +----- src/sage/sets/non_negative_integers.py | 1 + 22 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 13ea3b0338f..99a24c496c0 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -1878,13 +1878,13 @@ def image(self, domain_subset=None): cls = ImageSubobject return cls(self, domain_subset) + # Lazy imports to avoid circularity issues. Enumerated = LazyImport('sage.categories.enumerated_sets', 'EnumeratedSets', at_startup=True) - Facade = LazyImport('sage.categories.facade_sets', 'FacadeSets') Finite = LazyImport('sage.categories.finite_sets', 'FiniteSets', at_startup=True) Topological = LazyImport('sage.categories.topological_spaces', 'TopologicalSpaces', 'Topological', at_startup=True) - Metric = LazyImport('sage.categories.metric_spaces', 'MetricSpaces', - 'Metric', at_startup=True) + from sage.categories.facade_sets import FacadeSets as Facade + from sage.categories.metric_spaces import MetricSpaces as Metric class Infinite(CategoryWithAxiom): diff --git a/src/sage/combinat/cluster_complex.py b/src/sage/combinat/cluster_complex.py index 31562e6bc80..ba5645b14ca 100644 --- a/src/sage/combinat/cluster_complex.py +++ b/src/sage/combinat/cluster_complex.py @@ -50,7 +50,7 @@ from sage.categories.coxeter_groups import CoxeterGroups from sage.combinat.root_system.coxeter_group import CoxeterGroup from sage.combinat.subword_complex import SubwordComplex, SubwordComplexFacet -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN class ClusterComplexFacet(SubwordComplexFacet): diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index 18d80008ccb..1675cad6eff 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -51,7 +51,7 @@ TensorProductOfSuperCrystalsElement, TensorProductOfQueerSuperCrystalsElement) from sage.misc.flatten import flatten from sage.structure.element import get_coercion_model -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.arith.misc import integer_trunc as trunc diff --git a/src/sage/combinat/integer_lists/nn.py b/src/sage/combinat/integer_lists/nn.py index 6fe0cb0adb5..4329c6164d9 100644 --- a/src/sage/combinat/integer_lists/nn.py +++ b/src/sage/combinat/integer_lists/nn.py @@ -1,6 +1,6 @@ from sage.sets.family import Family from sage.combinat.integer_lists import IntegerListsLex -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index 352ec520d4d..2b88a6e3e1e 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -44,7 +44,7 @@ from sage.rings.infinity import PlusInfinity from sage.arith.all import binomial from sage.rings.integer_ring import ZZ -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.rings.integer import Integer def is_gale_ryser(r,s): diff --git a/src/sage/combinat/integer_vectors_mod_permgroup.py b/src/sage/combinat/integer_vectors_mod_permgroup.py index 26559396b39..229b5b28146 100644 --- a/src/sage/combinat/integer_vectors_mod_permgroup.py +++ b/src/sage/combinat/integer_vectors_mod_permgroup.py @@ -11,7 +11,7 @@ # **************************************************************************** from sage.structure.unique_representation import UniqueRepresentation -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets diff --git a/src/sage/combinat/interval_posets.py b/src/sage/combinat/interval_posets.py index cd0846e3d6e..7ccede75e8b 100644 --- a/src/sage/combinat/interval_posets.py +++ b/src/sage/combinat/interval_posets.py @@ -48,7 +48,7 @@ from sage.misc.latex import latex from sage.misc.lazy_attribute import lazy_attribute from sage.rings.integer import Integer -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.sets.non_negative_integers import NonNegativeIntegers from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets from sage.sets.family import Family diff --git a/src/sage/combinat/non_decreasing_parking_function.py b/src/sage/combinat/non_decreasing_parking_function.py index b5ecf2bb8c0..9763793bb1b 100644 --- a/src/sage/combinat/non_decreasing_parking_function.py +++ b/src/sage/combinat/non_decreasing_parking_function.py @@ -38,7 +38,7 @@ from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.categories.sets_with_grading import SetsWithGrading from sage.categories.monoids import Monoids -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.rings.integer import Integer from sage.structure.element import Element from sage.structure.parent import Parent diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 844de2016da..6f52d1ae49d 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -307,7 +307,7 @@ from sage.rings.finite_rings.integer_mod_ring import IntegerModRing from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.arith.all import factorial, gcd from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.integer import Integer diff --git a/src/sage/combinat/partition_kleshchev.py b/src/sage/combinat/partition_kleshchev.py index 8fd46bc76fe..7c8344d71b5 100644 --- a/src/sage/combinat/partition_kleshchev.py +++ b/src/sage/combinat/partition_kleshchev.py @@ -85,7 +85,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.rings.finite_rings.integer_mod_ring import IntegerModRing from sage.rings.integer_ring import ZZ -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.cpython.getattr import getattr_from_other_class from collections import defaultdict diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 800e2a61c31..389063e0d24 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -267,7 +267,7 @@ class of modules for the algebras, which are generalisations of the Specht from sage.misc.cachefunc import cached_method from sage.rings.finite_rings.integer_mod_ring import IntegerModRing from sage.rings.integer_ring import ZZ -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.rings.integer import Integer from sage.sets.positive_integers import PositiveIntegers from sage.structure.parent import Parent diff --git a/src/sage/combinat/path_tableaux/semistandard.py b/src/sage/combinat/path_tableaux/semistandard.py index 55236fe9ef8..84de742f1d0 100644 --- a/src/sage/combinat/path_tableaux/semistandard.py +++ b/src/sage/combinat/path_tableaux/semistandard.py @@ -85,7 +85,7 @@ from sage.combinat.tableau import Tableau from sage.combinat.gelfand_tsetlin_patterns import GelfandTsetlinPattern from sage.combinat.partition import _Partitions -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN ############################################################################### diff --git a/src/sage/combinat/posets/poset_examples.py b/src/sage/combinat/posets/poset_examples.py index 377fc569fde..7f1fcf1e633 100644 --- a/src/sage/combinat/posets/poset_examples.py +++ b/src/sage/combinat/posets/poset_examples.py @@ -623,7 +623,7 @@ def IntegerPartitionsDominanceOrder(n): [[4, 2], [5, 1]], [[5, 1], [6]]] """ - from sage.rings.semirings.all import NN + from sage.rings.semirings.non_negative_integer_semiring import NN if n not in NN: raise ValueError('n must be an integer') from sage.combinat.partition import Partitions, Partition @@ -973,7 +973,7 @@ def SetPartitions(n): sage: posets.SetPartitions(4) Finite lattice containing 15 elements """ - from sage.rings.semirings.all import NN + from sage.rings.semirings.non_negative_integer_semiring import NN if n not in NN: raise ValueError('n must be an integer') from sage.combinat.set_partition import SetPartitions diff --git a/src/sage/combinat/root_system/cartan_type.py b/src/sage/combinat/root_system/cartan_type.py index dafe2f62366..68c4d8ae15f 100644 --- a/src/sage/combinat/root_system/cartan_type.py +++ b/src/sage/combinat/root_system/cartan_type.py @@ -594,7 +594,7 @@ def __call__(self, *args): if isinstance(t, (CartanType_abstract, SuperCartanType_standard)): return t - from sage.rings.semirings.all import NN + from sage.rings.semirings.non_negative_integer_semiring import NN if isinstance(t, str): if "x" in t: from . import type_reducible diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 8e97587945d..fd32dc072f1 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -3848,7 +3848,7 @@ def is_dominant_weight(self): # Or is_dominant_integral_weight? True """ alphacheck = self.parent().simple_coroots() - from sage.rings.semirings.all import NN + from sage.rings.semirings.non_negative_integer_semiring import NN return all(self.inner_product(alphacheck[i]) in NN for i in self.parent().index_set()) diff --git a/src/sage/combinat/root_system/type_super_A.py b/src/sage/combinat/root_system/type_super_A.py index 16df61caee3..c398812c087 100644 --- a/src/sage/combinat/root_system/type_super_A.py +++ b/src/sage/combinat/root_system/type_super_A.py @@ -442,7 +442,7 @@ def is_dominant_weight(self): """ alpha = self.parent().simple_roots() l = self.parent().cartan_type().symmetrizer() - from sage.rings.semirings.all import NN + from sage.rings.semirings.non_negative_integer_semiring import NN return all(l[i] * self.inner_product(alpha[i]) in NN for i in self.parent().index_set()) diff --git a/src/sage/combinat/sine_gordon.py b/src/sage/combinat/sine_gordon.py index c9a4ab2d8ba..52895b255c5 100644 --- a/src/sage/combinat/sine_gordon.py +++ b/src/sage/combinat/sine_gordon.py @@ -45,7 +45,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.real_mpfr import RR -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.functions.trig import cos, sin from sage.misc.lazy_import import lazy_import lazy_import("sage.plot.plot", "parametric_plot") diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index c3d67555a6b..b8a0bebab44 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -215,7 +215,7 @@ def SymmetricGroupAlgebra(R, W, category=None): sage: SGg.group().conjugacy_classes_representatives() [(), (1,2), (1,2,3)] """ - from sage.rings.semirings.all import NN + from sage.rings.semirings.non_negative_integer_semiring import NN if W in NN: W = Permutations(W) if category is None: diff --git a/src/sage/combinat/tableau_tuple.py b/src/sage/combinat/tableau_tuple.py index 34565ecb642..3deb562bbb6 100644 --- a/src/sage/combinat/tableau_tuple.py +++ b/src/sage/combinat/tableau_tuple.py @@ -231,7 +231,7 @@ from sage.arith.all import factorial from sage.rings.finite_rings.integer_mod_ring import IntegerModRing from sage.rings.integer import Integer -from sage.rings.semirings.all import NN +from sage.rings.semirings.non_negative_integer_semiring import NN from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets from sage.sets.family import Family from sage.sets.positive_integers import PositiveIntegers diff --git a/src/sage/combinat/words/abstract_word.py b/src/sage/combinat/words/abstract_word.py index 80dab5e552b..ff3be9930ad 100644 --- a/src/sage/combinat/words/abstract_word.py +++ b/src/sage/combinat/words/abstract_word.py @@ -833,7 +833,7 @@ def delta(self): word: 1211222112112112221122211222112112112221... """ from sage.combinat.words.word import Word - from sage.rings.semirings.all import NN + from sage.rings.semirings.non_negative_integer_semiring import NN return Word(self._delta_iterator(), alphabet=NN) def _iterated_right_palindromic_closure_iterator(self, f=None): diff --git a/src/sage/rings/semirings/all.py b/src/sage/rings/semirings/all.py index 91594074088..b14da38328a 100644 --- a/src/sage/rings/semirings/all.py +++ b/src/sage/rings/semirings/all.py @@ -1,6 +1,2 @@ - -from sage.misc.lazy_import import lazy_import -lazy_import('sage.rings.semirings.non_negative_integer_semiring', - ['NonNegativeIntegerSemiring', 'NN']) - +from .non_negative_integer_semiring import NonNegativeIntegerSemiring, NN from .tropical_semiring import TropicalSemiring diff --git a/src/sage/sets/non_negative_integers.py b/src/sage/sets/non_negative_integers.py index 9b01ad6f3d4..9b5def119e0 100644 --- a/src/sage/sets/non_negative_integers.py +++ b/src/sage/sets/non_negative_integers.py @@ -95,6 +95,7 @@ def __contains__(self, elt): """ EXAMPLES:: + sage: NN = NonNegativeIntegers() sage: 1 in NN True sage: -1 in NN From 539119b1bc7d9fad671f4e5a8b1dc536cd718e0b Mon Sep 17 00:00:00 2001 From: "John H. Palmieri" Date: Sat, 12 Nov 2022 17:41:49 -0800 Subject: [PATCH 403/414] trac 34652: Add categories/facade_sets to pkg/sagemath-objects/Manifest.in --- pkgs/sagemath-categories/MANIFEST.in.m4 | 1 + pkgs/sagemath-objects/MANIFEST.in | 1 + src/sage/categories/sets_cat.py | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/sagemath-categories/MANIFEST.in.m4 b/pkgs/sagemath-categories/MANIFEST.in.m4 index 390c7e5759f..98d10a91fef 100644 --- a/pkgs/sagemath-categories/MANIFEST.in.m4 +++ b/pkgs/sagemath-categories/MANIFEST.in.m4 @@ -12,6 +12,7 @@ exclude sage/categories/basic.* exclude sage/categories/cartesian_product.* exclude sage/categories/category*.* exclude sage/categories/covariant_functorial_construction.* +exclude sage/categories/facade_sets.* exclude sage/categories/functor.* exclude sage/categories/homset.* exclude sage/categories/homsets.* diff --git a/pkgs/sagemath-objects/MANIFEST.in b/pkgs/sagemath-objects/MANIFEST.in index ffa9e9c7f10..eced95ea865 100644 --- a/pkgs/sagemath-objects/MANIFEST.in +++ b/pkgs/sagemath-objects/MANIFEST.in @@ -12,6 +12,7 @@ include sage/categories/basic.* include sage/categories/cartesian_product.* include sage/categories/category*.* include sage/categories/covariant_functorial_construction.* +include sage/categories/facade_sets.* include sage/categories/functor.* include sage/categories/homset.* include sage/categories/homsets.* diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 99a24c496c0..9f9bab84e23 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -1883,8 +1883,9 @@ def image(self, domain_subset=None): Finite = LazyImport('sage.categories.finite_sets', 'FiniteSets', at_startup=True) Topological = LazyImport('sage.categories.topological_spaces', 'TopologicalSpaces', 'Topological', at_startup=True) + Metric = LazyImport('sage.categories.metric_spaces', 'MetricSpaces', + 'Metric', at_startup=True) from sage.categories.facade_sets import FacadeSets as Facade - from sage.categories.metric_spaces import MetricSpaces as Metric class Infinite(CategoryWithAxiom): From 74601e6a3a42413400b2718ad2fed987028a30ca Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 13 Nov 2022 10:10:08 -0800 Subject: [PATCH 404/414] Remove unused import --- src/sage/manifolds/differentiable/vectorfield_module.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 8cd62dacc8c..ac8e7dc1290 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -54,10 +54,7 @@ from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule -from sage.tensor.modules.reflexive_module import ( - ReflexiveModule_abstract, - ReflexiveModule_base -) +from sage.tensor.modules.reflexive_module import ReflexiveModule_base if TYPE_CHECKING: from sage.manifolds.differentiable.diff_map import DiffMap From d18036b4148283139e7c66fb57f92468c0470715 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 13 Nov 2022 16:42:51 -0800 Subject: [PATCH 405/414] build/pkgs/cmake: Update to 3.24.3 --- build/pkgs/cmake/checksums.ini | 6 +++--- build/pkgs/cmake/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/cmake/checksums.ini b/build/pkgs/cmake/checksums.ini index 64ac39d871a..c89abdf4277 100644 --- a/build/pkgs/cmake/checksums.ini +++ b/build/pkgs/cmake/checksums.ini @@ -1,5 +1,5 @@ tarball=cmake-VERSION.tar.gz -sha1=abbeedb49c153be4103eabc95f4ffd94440f4d61 -md5=f616604606184e3c7b870a57e68a7c3b -cksum=2102786355 +sha1=256d6a57a57fa6ceaacd6a2daf708baefd33850c +md5=226dd564164372f9f7d1e21e38e6e8c5 +cksum=2080281918 upstream_url=https://github.com/Kitware/CMake/releases/download/vVERSION/cmake-VERSION.tar.gz diff --git a/build/pkgs/cmake/package-version.txt b/build/pkgs/cmake/package-version.txt index 6075c9a9ff9..693bd59e3e6 100644 --- a/build/pkgs/cmake/package-version.txt +++ b/build/pkgs/cmake/package-version.txt @@ -1 +1 @@ -3.21.0 +3.24.3 From dce791c7544c32388d34c7bfadaede61e51a1e92 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 13 Nov 2022 16:44:50 -0800 Subject: [PATCH 406/414] build/pkgs/cmake/spkg-configure.m4: Increase minimum version from 3.4 to 3.10 (as found on ubuntu-bionic) --- build/pkgs/cmake/spkg-configure.m4 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/cmake/spkg-configure.m4 b/build/pkgs/cmake/spkg-configure.m4 index bc50446d0ef..8934bfc273f 100644 --- a/build/pkgs/cmake/spkg-configure.m4 +++ b/build/pkgs/cmake/spkg-configure.m4 @@ -1,11 +1,11 @@ SAGE_SPKG_CONFIGURE( [cmake], [ - AC_CACHE_CHECK([for cmake >= 3.4], [ac_cv_path_CMAKE], [ + AC_CACHE_CHECK([for cmake >= 3.10], [ac_cv_path_CMAKE], [ AC_PATH_PROGS_FEATURE_CHECK([CMAKE], [cmake], [ cmake_version=`$ac_path_CMAKE --version 2>&1 \ | $SED -n -e 's/cmake version *\([[0-9]]*\.[[0-9]]*\.[[0-9]]*\)/\1/p'` AS_IF([test -n "$cmake_version"], [ - AX_COMPARE_VERSION([$cmake_version], [ge], [3.4], [ + AX_COMPARE_VERSION([$cmake_version], [ge], [3.10], [ ac_cv_path_CMAKE="$ac_path_CMAKE" ac_path_CMAKE_found=: ]) From 74b4574ec0b9803abecfa348ea79c0cdc60f07e2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 13 Nov 2022 20:23:23 -0800 Subject: [PATCH 407/414] build/pkgs/_python3.*/distros/debian.txt: Add python3.*-distutils --- build/pkgs/_python3.10/distros/debian.txt | 1 + build/pkgs/_python3.11/distros/debian.txt | 1 + build/pkgs/_python3.8/distros/debian.txt | 1 + build/pkgs/_python3.9/distros/debian.txt | 1 + 4 files changed, 4 insertions(+) diff --git a/build/pkgs/_python3.10/distros/debian.txt b/build/pkgs/_python3.10/distros/debian.txt index 9b7529828a1..a3e949f403c 100644 --- a/build/pkgs/_python3.10/distros/debian.txt +++ b/build/pkgs/_python3.10/distros/debian.txt @@ -1,3 +1,4 @@ python3.10 python3.10-dev +python3.10-distutils python3.10-venv diff --git a/build/pkgs/_python3.11/distros/debian.txt b/build/pkgs/_python3.11/distros/debian.txt index a3128e4751f..8877c2d2af0 100644 --- a/build/pkgs/_python3.11/distros/debian.txt +++ b/build/pkgs/_python3.11/distros/debian.txt @@ -1,3 +1,4 @@ python3.11 python3.11-dev +python3.11-distutils python3.11-venv diff --git a/build/pkgs/_python3.8/distros/debian.txt b/build/pkgs/_python3.8/distros/debian.txt index 11476fe0595..bf46e908ff6 100644 --- a/build/pkgs/_python3.8/distros/debian.txt +++ b/build/pkgs/_python3.8/distros/debian.txt @@ -1,3 +1,4 @@ python3.8 python3.8-dev +python3.8-distutils python3.8-venv diff --git a/build/pkgs/_python3.9/distros/debian.txt b/build/pkgs/_python3.9/distros/debian.txt index e113156b021..014b90fd8ad 100644 --- a/build/pkgs/_python3.9/distros/debian.txt +++ b/build/pkgs/_python3.9/distros/debian.txt @@ -1,3 +1,4 @@ python3.9 python3.9-dev +python3.9-distutils python3.9-venv From f53288683dc6c870c9ea0105c37c4b8f18b3cbfd Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 13 Nov 2022 21:25:28 -0800 Subject: [PATCH 408/414] tox.ini: Add ubuntu-deadsnakes, python3.12 --- build/bin/write-dockerfile.sh | 8 ++++++-- build/pkgs/_python3.12/distros/arch.txt | 1 + build/pkgs/_python3.12/distros/cygwin.txt | 1 + build/pkgs/_python3.12/distros/debian.txt | 4 ++++ build/pkgs/_python3.12/distros/fedora.txt | 2 ++ build/pkgs/_python3.12/distros/freebsd.txt | 1 + build/pkgs/_python3.12/distros/homebrew.txt | 1 + build/pkgs/_python3.12/distros/macports.txt | 1 + build/pkgs/_python3.12/distros/opensuse.txt | 2 ++ tox.ini | 13 ++++++++----- 10 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 build/pkgs/_python3.12/distros/arch.txt create mode 100644 build/pkgs/_python3.12/distros/cygwin.txt create mode 100644 build/pkgs/_python3.12/distros/debian.txt create mode 100644 build/pkgs/_python3.12/distros/fedora.txt create mode 100644 build/pkgs/_python3.12/distros/freebsd.txt create mode 100644 build/pkgs/_python3.12/distros/homebrew.txt create mode 100644 build/pkgs/_python3.12/distros/macports.txt create mode 100644 build/pkgs/_python3.12/distros/opensuse.txt diff --git a/build/bin/write-dockerfile.sh b/build/bin/write-dockerfile.sh index 42eb5f53686..14cbaf786eb 100755 --- a/build/bin/write-dockerfile.sh +++ b/build/bin/write-dockerfile.sh @@ -63,11 +63,15 @@ EOF RUN sed -i.bak $DIST_UPGRADE /etc/apt/sources.list && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade EOF fi - if [ -n "$EXTRA_REPOSITORY" ]; then + if [ -n "$EXTRA_REPOSITORIES" ]; then cat < Date: Sun, 13 Nov 2022 21:45:47 -0800 Subject: [PATCH 409/414] build/pkgs/cmake/spkg-configure.m4: Increase minimum version to 3.11 --- build/pkgs/cmake/spkg-configure.m4 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pkgs/cmake/spkg-configure.m4 b/build/pkgs/cmake/spkg-configure.m4 index 8934bfc273f..ce36e8aa0cc 100644 --- a/build/pkgs/cmake/spkg-configure.m4 +++ b/build/pkgs/cmake/spkg-configure.m4 @@ -1,11 +1,11 @@ SAGE_SPKG_CONFIGURE( [cmake], [ - AC_CACHE_CHECK([for cmake >= 3.10], [ac_cv_path_CMAKE], [ + AC_CACHE_CHECK([for cmake >= 3.11], [ac_cv_path_CMAKE], [ AC_PATH_PROGS_FEATURE_CHECK([CMAKE], [cmake], [ cmake_version=`$ac_path_CMAKE --version 2>&1 \ | $SED -n -e 's/cmake version *\([[0-9]]*\.[[0-9]]*\.[[0-9]]*\)/\1/p'` AS_IF([test -n "$cmake_version"], [ - AX_COMPARE_VERSION([$cmake_version], [ge], [3.10], [ + AX_COMPARE_VERSION([$cmake_version], [ge], [3.11], [ ac_cv_path_CMAKE="$ac_path_CMAKE" ac_path_CMAKE_found=: ]) From 501a25411904ead8d9b097f465cd40b984eaa701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 14 Nov 2022 16:20:02 +0100 Subject: [PATCH 410/414] Implement `characteristic` method for rings extensions See ticket #34692. --- src/sage/rings/ring_extension.pyx | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index 2b649359ad4..9f53d42f80f 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -1885,6 +1885,48 @@ cdef class RingExtension_generic(CommutativeAlgebra): parent = self.Hom(codomain, category=category) return RingExtensionHomomorphism(parent, im_gens, base_map, check) + def characteristic(self): + r""" + Return the characteristic of the extension as a ring. + + OUTPUT: + + A prime number. + + EXAMPLES:: + + sage: F = GF(5^2).over() # over GF(5) + sage: K = GF(5^4).over(F) + sage: L = GF(5^12).over(K) + sage: F.characteristic() + 5 + sage: K.characteristic() + 5 + sage: L.characteristic() + 5 + + :: + + sage: F = RR.over(ZZ) + sage: F.characteristic() + 0 + + :: + + sage: F = GF(11) + sage: A. = F[] + sage: K = Frac(F).over(F) + sage: K.characteristic() + 11 + + :: + + sage: R = Zp(7).over(ZZ) + sage: R.characteristic() + 0 + """ + return self._backend.characteristic() + # Fraction fields ################# From bcffa0cf0842ebef2403d5c8b6aa88514beb86e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 14 Nov 2022 17:18:21 +0100 Subject: [PATCH 411/414] Fix imprecision in documentation See David's comment: https://trac.sagemath.org/ticket/34692#comment:5. --- src/sage/rings/ring_extension.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index 9f53d42f80f..bbc082d2e77 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -1891,7 +1891,7 @@ cdef class RingExtension_generic(CommutativeAlgebra): OUTPUT: - A prime number. + A prime number or zero. EXAMPLES:: From 675e24988a07f58d092a7ddb976c9e6ee04f3ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 14 Nov 2022 18:03:12 +0100 Subject: [PATCH 412/414] Add tests for characteristic method See https://trac.sagemath.org/ticket/34692#comment:9. --- src/sage/rings/ring_extension.pyx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index bbc082d2e77..c761314585c 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -1921,9 +1921,21 @@ cdef class RingExtension_generic(CommutativeAlgebra): :: - sage: R = Zp(7).over(ZZ) - sage: R.characteristic() - 0 + sage: E = Fp(7).over(ZZ) + sage: E.characteristic() + 7 + + TESTS:: + + Ensure ticket :trac:`34692` is fixed. + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: k = Frac(FqX) + sage: i = Hom(FqX, k).natural_map() + sage: K = k.over(i) + sage: K.frobenius_endomorphism() + Frobenius endomorphism x |--> x^11 of Fraction Field of Univariate Polynomial Ring in X over Finite Field of size 11 over its base """ return self._backend.characteristic() From ab2c211033e15fa5237b20690a715d50577d06f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 14 Nov 2022 18:52:42 +0100 Subject: [PATCH 413/414] Fix doctest See https://trac.sagemath.org/ticket/34692#comment:11. --- src/sage/rings/ring_extension.pyx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index c761314585c..b493f57bed9 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -1921,19 +1921,18 @@ cdef class RingExtension_generic(CommutativeAlgebra): :: - sage: E = Fp(7).over(ZZ) + sage: E = GF(7).over(ZZ) sage: E.characteristic() 7 - TESTS:: + TESTS: - Ensure ticket :trac:`34692` is fixed. + Ensure ticket :trac:`34692` is fixed:: sage: Fq = GF(11) sage: FqX. = Fq[] sage: k = Frac(FqX) - sage: i = Hom(FqX, k).natural_map() - sage: K = k.over(i) + sage: K = k.over(FqX) sage: K.frobenius_endomorphism() Frobenius endomorphism x |--> x^11 of Fraction Field of Univariate Polynomial Ring in X over Finite Field of size 11 over its base """ From 84f02afa5c85e98aeba55c13e09d414871e6e35e Mon Sep 17 00:00:00 2001 From: Release Manager Date: Mon, 21 Nov 2022 22:25:54 +0100 Subject: [PATCH 414/414] Updated SageMath version to 9.8.beta4 --- .zenodo.json | 8 ++++---- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 6 +++--- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/sage_conf/install-requires.txt | 2 +- build/pkgs/sage_docbuild/install-requires.txt | 2 +- build/pkgs/sage_setup/install-requires.txt | 2 +- build/pkgs/sage_sws2rst/install-requires.txt | 2 +- build/pkgs/sagelib/install-requires.txt | 2 +- build/pkgs/sagemath_categories/install-requires.txt | 2 +- build/pkgs/sagemath_environment/install-requires.txt | 2 +- build/pkgs/sagemath_objects/install-requires.txt | 2 +- build/pkgs/sagemath_repl/install-requires.txt | 2 +- pkgs/sage-conf/VERSION.txt | 2 +- pkgs/sage-conf_pypi/VERSION.txt | 2 +- pkgs/sage-docbuild/VERSION.txt | 2 +- pkgs/sage-setup/VERSION.txt | 2 +- pkgs/sage-sws2rst/VERSION.txt | 2 +- pkgs/sagemath-categories/VERSION.txt | 2 +- pkgs/sagemath-environment/VERSION.txt | 2 +- pkgs/sagemath-objects/VERSION.txt | 2 +- pkgs/sagemath-repl/VERSION.txt | 2 +- src/VERSION.txt | 2 +- src/bin/sage-version.sh | 6 +++--- src/sage/version.py | 6 +++--- 25 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 81ea9dde6d0..4e6641194eb 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,10 +1,10 @@ { "description": "Mirror of the Sage https://sagemath.org/ source tree", "license": "other-open", - "title": "sagemath/sage: 9.8.beta3", - "version": "9.8.beta3", + "title": "sagemath/sage: 9.8.beta4", + "version": "9.8.beta4", "upload_type": "software", - "publication_date": "2022-10-30", + "publication_date": "2022-11-21", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.8.beta3", + "identifier": "https://github.com/sagemath/sage/tree/9.8.beta4", "relation": "isSupplementTo" }, { diff --git a/VERSION.txt b/VERSION.txt index ff069aea672..ba4e46788bd 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.8.beta3, Release Date: 2022-10-30 +SageMath version 9.8.beta4, Release Date: 2022-11-21 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 5e03f886f84..9b6e528d13e 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=5c6eda541f708e7bbb272abbf285a492fa743128 -md5=b7fe25797f79a090262df39cc0a93c0c -cksum=1970835260 +sha1=013bfce4d1d0d0d68553c4025647445b7036f4db +md5=a429cd9c90d6bd90f7b2254b7fed8669 +cksum=1487098037 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 8b531a65419..3be6f9a021c 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -e063389aa995fb79f79f5e4cf2bb413f2a1e0b35 +03f68f4aa9602d246824f764e2ea328fa52577c7 diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index e1ce4a85f34..c890e648a67 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 9.8b3 +sage-conf ~= 9.8b4 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 3db13eeb1f7..35da0e99bbb 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 9.8b3 +sage-docbuild ~= 9.8b4 diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index d8a7c30eba0..c020dd4b7fc 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 9.8b3 +sage-setup ~= 9.8b4 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index 95d6d392aa5..e2197377b3f 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 9.8b3 +sage-sws2rst ~= 9.8b4 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 3016d9eebcc..c66a399a69c 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagelib ~= 9.8b3 +sagelib ~= 9.8b4 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index d1bce6cf96e..ba664945cd1 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 9.8b3 +sagemath-categories ~= 9.8b4 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 99458e3a96c..aa6c86145f3 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 9.8b3 +sagemath-environment ~= 9.8b4 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index e0b880eaac0..14d20cf2459 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 9.8b3 +sagemath-objects ~= 9.8b4 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index 3cd52abc7d6..c03509f6f5c 100644 --- a/build/pkgs/sagemath_repl/install-requires.txt +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 9.8b3 +sagemath-repl ~= 9.8b4 diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/src/VERSION.txt b/src/VERSION.txt index b0789c4fde8..ad07d6bad83 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.8.beta3 +9.8.beta4 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index b4ab677a7f3..a32acb79bcb 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='9.8.beta3' -SAGE_RELEASE_DATE='2022-10-30' -SAGE_VERSION_BANNER='SageMath version 9.8.beta3, Release Date: 2022-10-30' +SAGE_VERSION='9.8.beta4' +SAGE_RELEASE_DATE='2022-11-21' +SAGE_VERSION_BANNER='SageMath version 9.8.beta4, Release Date: 2022-11-21' diff --git a/src/sage/version.py b/src/sage/version.py index a82d4578e10..aeb6bb8f0f5 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '9.8.beta3' -date = '2022-10-30' -banner = 'SageMath version 9.8.beta3, Release Date: 2022-10-30' +version = '9.8.beta4' +date = '2022-11-21' +banner = 'SageMath version 9.8.beta4, Release Date: 2022-11-21'