def __init__(self, L, q=None): """ Initialize ``self``. TESTS:: sage: L = posets.BooleanLattice(4) sage: M = L.quantum_moebius_algebra() sage: TestSuite(M).run() # long time sage: from sage.combinat.posets.moebius_algebra import QuantumMoebiusAlgebra sage: L = posets.Crown(2) sage: QuantumMoebiusAlgebra(L) Traceback (most recent call last): ... ValueError: L must be a lattice """ if not L.is_lattice(): raise ValueError("L must be a lattice") if q is None: q = LaurentPolynomialRing(ZZ, 'q').gen() self._q = q R = q.parent() cat = Algebras(R).WithBasis() if L in FiniteEnumeratedSets(): cat = cat.Commutative().FiniteDimensional() self._lattice = L self._category = cat Parent.__init__(self, base=R, category=self._category.WithRealizations())
def __classcall_private__(cls, R, q=None): r""" Normalize input to ensure a unique representation. TESTS:: sage: R.<q> = LaurentPolynomialRing(QQ) sage: AW1 = algebras.AskeyWilson(QQ) sage: AW2 = algebras.AskeyWilson(R, q) sage: AW1 is AW2 True sage: AW = algebras.AskeyWilson(ZZ, 0) Traceback (most recent call last): ... ValueError: q cannot be 0 sage: AW = algebras.AskeyWilson(ZZ, 3) Traceback (most recent call last): ... ValueError: q=3 is not invertible in Integer Ring """ if q is None: R = LaurentPolynomialRing(R, 'q') q = R.gen() else: q = R(q) if q == 0: raise ValueError("q cannot be 0") if 1 / q not in R: raise ValueError("q={} is not invertible in {}".format(q, R)) if R not in Rings().Commutative(): raise ValueError("{} is not a commutative ring".format(R)) return super(AskeyWilsonAlgebra, cls).__classcall__(cls, R, q)
def __classcall__(cls, q=None, bar=None, R=None, **kwds): """ Normalize input to ensure a unique representation. EXAMPLES:: sage: R.<q> = LaurentPolynomialRing(ZZ) sage: O1 = algebras.QuantumMatrixCoordinate(4) sage: O2 = algebras.QuantumMatrixCoordinate(4, 4, q=q) sage: O3 = algebras.QuantumMatrixCoordinate(4, R=ZZ) sage: O4 = algebras.QuantumMatrixCoordinate(4, R=R, q=q) sage: O1 is O2 and O2 is O3 and O3 is O4 True sage: O5 = algebras.QuantumMatrixCoordinate(4, R=QQ) sage: O1 is O5 False """ if R is None: R = ZZ else: if q is not None: q = R(q) if q is None: q = LaurentPolynomialRing(R, 'q').gen() return super(QuantumMatrixCoordinateAlgebra_abstract, cls).__classcall__(cls, q=q, bar=bar, R=q.parent(), **kwds)
def __classcall_private__(cls, R, q=None): r""" Normalize input to ensure a unique representation. TESTS:: sage: R.<q> = LaurentPolynomialRing(QQ) sage: AW1 = algebras.AskeyWilson(QQ) sage: AW2 = algebras.AskeyWilson(R, q) sage: AW1 is AW2 True sage: AW = algebras.AskeyWilson(ZZ, 0) Traceback (most recent call last): ... ValueError: q cannot be 0 sage: AW = algebras.AskeyWilson(ZZ, 3) Traceback (most recent call last): ... ValueError: q=3 is not invertible in Integer Ring """ if q is None: R = LaurentPolynomialRing(R, 'q') q = R.gen() else: q = R(q) if q == 0: raise ValueError("q cannot be 0") if 1/q not in R: raise ValueError("q={} is not invertible in {}".format(q, R)) if R not in Rings().Commutative(): raise ValueError("{} is not a commutative ring".format(R)) return super(AskeyWilsonAlgebra, cls).__classcall__(cls, R, q)
def group_ring(triangulation, angle_structure, cycles, alpha=False, ring=ZZ): S, U, V = faces_in_smith(triangulation, angle_structure, cycles) rank, _, _ = rank_of_quotient(S) if alpha: assert rank < 26 return LaurentPolynomialRing(ring, list(ascii)[:rank], rank) else: return LaurentPolynomialRing(ring, "x", rank)
def q_int(n, q=None): r""" Return the `q`-analog of the nonnegative integer `n`. The `q`-analog of the nonnegative integer `n` is given by .. MATH:: [n]_q = \frac{q^n - q^{-n}}{q - q^{-1}} = q^{n-1} + q^{n-3} + \cdots + q^{-n+3} + q^{-n+1}. INPUT: - ``n`` -- the nonnegative integer `n` defined above - ``q`` -- (default: `q \in \ZZ[q, q^{-1}]`) the parameter `q` (should be invertible) If ``q`` is unspecified, then it defaults to using the generator `q` for a Laurent polynomial ring over the integers. .. NOTE:: This is not the "usual" `q`-analog of `n` (or `q`-integer) but a variant useful for quantum groups. For the version used in combinatorics, see :mod:`sage.combinat.q_analogues`. EXAMPLES:: sage: from sage.algebras.quantum_groups.q_numbers import q_int sage: q_int(2) q^-1 + q sage: q_int(3) q^-2 + 1 + q^2 sage: q_int(5) q^-4 + q^-2 + 1 + q^2 + q^4 sage: q_int(5, 1) 5 TESTS:: sage: from sage.algebras.quantum_groups.q_numbers import q_int sage: q_int(1) 1 sage: q_int(0) 0 """ if q is None: R = LaurentPolynomialRing(ZZ, 'q') q = R.gen() else: R = q.parent() if n == 0: return R.zero() return R.sum(q**(n - 2 * i - 1) for i in range(n))
def burau_matrix(self, var='t'): """ Return the Burau matrix of the braid. INPUT: - ``var`` -- string (default: ``'t'``). The name of the variable in the entries of the matrix. OUTPUT: The Burau matrix of the braid. It is a matrix whose entries are Laurent polynomials in the variable ``var``. EXAMPLES:: sage: B = BraidGroup(4) sage: B.inject_variables() Defining s0, s1, s2 sage: b=s0*s1/s2/s1 sage: b.burau_matrix() [ -t + 1 0 -t^2 + t t^2] [ 1 0 0 0] [ 0 0 1 0] [ 0 t^-2 t^-1 - t^-2 1 - t^-1] sage: s2.burau_matrix('x') [ 1 0 0 0] [ 0 1 0 0] [ 0 0 -x + 1 x] [ 0 0 1 0] REFERENCES: http://en.wikipedia.org/wiki/Burau_representation """ R = LaurentPolynomialRing(IntegerRing(), var) t = R.gen() M = identity_matrix(R, self.strands()) for i in self.Tietze(): A = identity_matrix(R, self.strands()) if i>0: A[i-1, i-1] = 1-t A[i, i] = 0 A[i, i-1] = 1 A[i-1, i] = t if i<0: A[-1-i, -1-i] = 0 A[-i, -i] = 1-t**(-1) A[-1-i, -i] = 1 A[-i, -1-i] = t**(-1) M=M*A return M
def testJonesPolynomial(self): L = LaurentPolynomialRing(QQ,'q') q = L.gen() data = [('K3_1', ('q^3 + q - 1', -4)), ('K7_2', ('q^7 - q^6 + 2*q^5 - 2*q^4 + 2*q^3 - q^2 + q - 1', -8)), ('K8_3', ('q^8 - q^7 + 2*q^6 - 3*q^5 + 3*q^4 - 3*q^3 + 2*q^2 - q + 1', -4)), ('K8_13', ('-q^8 + 2*q^7 - 3*q^6 + 5*q^5 - 5*q^4 + 5*q^3 - 4*q^2 + 3*q - 1', -3)), ('L6a2', ('-q^6 + q^5 - 2*q^4 + 2*q^3 - 2*q^2 + q - 1', 1)), ('L6a4', ('-q^6 + 3*q^5 - 2*q^4 + 4*q^3 - 2*q^2 + 3*q - 1', -3)), ('L7a3', ('-q^7 + q^6 - 3*q^5 + 2*q^4 - 3*q^3 + 3*q^2 - 2*q + 1', -7)), ('L10n1', ('q^8 - 2*q^7 + 2*q^6 - 4*q^5 + 3*q^4 - 3*q^3 + 2*q^2 - 2*q + 1', -2))] for link_name, (poly, exp) in data: link = getattr(self, link_name) self.assertEqual(link.jones_polynomial(), L(poly)*q**exp)
def construction(self): r""" Return the functorial construction of this Laurent power series ring. The construction is given as the completion of the Laurent polynomials. EXAMPLES:: sage: L.<t> = LaurentSeriesRing(ZZ, default_prec=42) sage: phi, arg = L.construction() sage: phi Completion[t, prec=42] sage: arg Univariate Laurent Polynomial Ring in t over Integer Ring sage: phi(arg) is L True Because of this construction, pushout is automatically available:: sage: 1/2 * t 1/2*t sage: parent(1/2 * t) Laurent Series Ring in t over Rational Field sage: QQbar.gen() * t I*t sage: parent(QQbar.gen() * t) Laurent Series Ring in t over Algebraic Field """ from sage.categories.pushout import CompletionFunctor from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing L = LaurentPolynomialRing(self.base_ring(), self._names[0]) return CompletionFunctor(self._names[0], self.default_prec()), L
def __classcall__(cls, *args): if len(args) == 1 and isinstance(args[0], AbstractMSumRing): poly_ring = args[0]._polynomial_ring else: if len(args) == 1 and not isinstance(args[0], (tuple, str)): args = (tuple(args[0]),) from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing poly_ring = LaurentPolynomialRing(QQ, *args) return super(AbstractMSumRing, cls).__classcall__(cls, poly_ring)
def alexander_matrix(self, mv=True): """ Returns the Alexander matrix of the link:: sage: L = Link('3_1') sage: A = L.alexander_matrix() sage: A # doctest: +SKIP ([ -1 t 1 - t] [1 - t -1 t] [ t 1 - t -1], [t, t, t]) sage: L = Link([(4,1,3,2),(1,4,2,3)]) sage: A = L.alexander_matrix() sage: A # doctest: +SKIP ([ -1 + t1^-1 t1^-1*t2 - t1^-1] [t1*t2^-1 - t2^-1 -1 + t2^-1], [t2, t1]) """ comp = len(self.link_components) if comp < 2: mv = False G = self.knot_group() num_gens = len(G.gens()) L_g = LaurentPolynomialRing(QQ, ['g%d' % (i + 1) for i in range(num_gens)]) g = list(L_g.gens()) if (mv): L_t = LaurentPolynomialRing(QQ, ['t%d' % (i + 1) for i in range(comp)]) t = list(L_t.gens()) #determine the component to which each variable corresponds g_component = [c.strand_components[2] for c in self.crossings] for i in range(len(g)): g[i] = t[g_component[i]] else: L_t = LaurentPolynomialRing(QQ, 't') t = L_t.gen() g = [t] * len(g) B = G.alexander_matrix(g) return (B, g)
def __classcall_private__(cls, d, n, q=None, R=None): """ Standardize input to ensure a unique representation. TESTS:: sage: Y1 = algebras.YokonumaHecke(5, 3) sage: q = LaurentPolynomialRing(QQ, 'q').gen() sage: Y2 = algebras.YokonumaHecke(5, 3, q) sage: Y3 = algebras.YokonumaHecke(5, 3, q, q.parent()) sage: Y1 is Y2 and Y2 is Y3 True """ if q is None: q = LaurentPolynomialRing(QQ, 'q').gen() if R is None: R = q.parent() q = R(q) if R not in Rings().Commutative(): raise TypeError("base ring must be a commutative ring") return super(YokonumaHeckeAlgebra, cls).__classcall__(cls, d, n, q, R)
def __init__(self, L, q=None): """ Initialize ``self``. TESTS:: sage: L = posets.BooleanLattice(4) sage: M = L.quantum_moebius_algebra() sage: TestSuite(M).run() # long time """ if not L.is_lattice(): raise ValueError("L must be a lattice") if q is None: q = LaurentPolynomialRing(ZZ, "q").gen() self._q = q R = q.parent() cat = Algebras(R).WithBasis() if L in FiniteEnumeratedSets(): cat = cat.Commutative().FiniteDimensional() self._lattice = L self._category = cat Parent.__init__(self, base=R, category=self._category.WithRealizations())
def laurent_polynomial_ring(self): r""" If this is the Laurent series ring `R((t))`, return the Laurent polynomial ring `R[t,1/t]`. EXAMPLES:: sage: R = LaurentSeriesRing(QQ, "x") sage: R.laurent_polynomial_ring() Univariate Laurent Polynomial Ring in x over Rational Field """ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing return LaurentPolynomialRing(self.base_ring(), self.variable_name(), sparse=self.is_sparse())
def __init__(self, base_ring, names, sparse=True, category=None): """ Initialize ``self``. TESTS:: sage: L = LazyLaurentSeriesRing(ZZ, 't') sage: elts = L.some_elements()[:-2] # skip the non-exact elements sage: TestSuite(L).run(elements=elts, skip=['_test_elements', '_test_associativity', '_test_distributivity', '_test_zero']) sage: L.category() Category of infinite commutative no zero divisors algebras over (euclidean domains and infinite enumerated sets and metric spaces) sage: L = LazyLaurentSeriesRing(QQ, 't') sage: L.category() Join of Category of complete discrete valuation fields and Category of commutative algebras over (number fields and quotient fields and metric spaces) and Category of infinite sets sage: L = LazyLaurentSeriesRing(ZZ['x,y'], 't') sage: L.category() Category of infinite commutative no zero divisors algebras over (unique factorization domains and commutative algebras over (euclidean domains and infinite enumerated sets and metric spaces) and infinite sets) sage: E.<x,y> = ExteriorAlgebra(QQ) sage: L = LazyLaurentSeriesRing(E, 't') # not tested """ self._sparse = sparse self._coeff_ring = base_ring # We always use the dense because our CS_exact is implemented densely self._laurent_poly_ring = LaurentPolynomialRing(base_ring, names) self._internal_poly_ring = self._laurent_poly_ring category = Algebras(base_ring.category()) if base_ring in Fields(): category &= CompleteDiscreteValuationFields() else: if "Commutative" in base_ring.category().axioms(): category = category.Commutative() if base_ring in IntegralDomains(): category &= IntegralDomains() if base_ring.is_zero(): category = category.Finite() else: category = category.Infinite() Parent.__init__(self, base=base_ring, names=names, category=category)
def __init__(self, *args): if len(args) == 1 and isinstance(args[0], AbstractMSumRing): self._laurent_polynomial_ring = args[0]._laurent_polynomial_ring self._free_module = args[0]._free_module self._laurent_polynomial_ring_extra_var = args[0]._laurent_polynomial_ring_extra_var else: from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.modules.free_module import FreeModule self._laurent_polynomial_ring = LaurentPolynomialRing(QQ, *args) dim = ZZ(self._laurent_polynomial_ring.ngens()) self._free_module = FreeModule(ZZ, dim) # univariate extension of the polynomial ring # (needed in several algorithms) self._laurent_polynomial_ring_extra_var = self._laurent_polynomial_ring['EXTRA_VAR'] Parent.__init__(self, category=Rings())
def testAlexanderPoly(self): from sage.all import var, sqrt L = LaurentPolynomialRing(QQ, 't') t = L.gen() a = var('a') L3v = LaurentPolynomialRing(QQ, ['t1', 't2', 't3']) t1, t2, t3 = L3v.gens() # method = 'wirt' self.assertEqual(self.Tref.alexander_polynomial(), 1 - t + t**2) self.assertEqual(self.K3_1.alexander_polynomial(), 1 - t + t**2) self.assertEqual(self.K7_2.alexander_polynomial(), 3 - 5 * t + 3 * t**2) self.assertEqual(self.K8_3.alexander_polynomial(), 4 - 9 * t + 4 * t**2) self.assertEqual(self.K8_13.alexander_polynomial(), 2 - 7 * t + 11 * t**2 - 7 * t**3 + 2 * t**4) self.assertEqual(self.L2a1.alexander_polynomial(), 1) self.assertEqual( self.Borr.alexander_polynomial(), t1 * t2 * t3 - t1 * t2 - t1 * t3 - t2 * t3 + t1 + t2 + t3 - 1) self.assertEqual( self.L6a4.alexander_polynomial(), t1 * t2 * t3 - t1 * t2 - t1 * t3 - t2 * t3 + t1 + t2 + t3 - 1) # method = 'snappy' try: import snappy self.assertEqual(self.Tref.alexander_polynomial(method='snappy'), a**2 - a + 1) self.assertEqual(self.K3_1.alexander_polynomial(method='snappy'), a**2 - a + 1) self.assertEqual(self.K7_2.alexander_polynomial(method='snappy'), 3 * a**2 - 5 * a + 3) self.assertEqual(self.K8_3.alexander_polynomial(method='snappy'), 4 * a**2 - 9 * a + 4) self.assertEqual(self.K8_13.alexander_polynomial(method='snappy'), 2 * a**4 - 7 * a**3 + 11 * a**2 - 7 * a + 2) except ImportError: pass
def Jones_poly(K, variable=None, new_convention=False): """ The old convention should really have powers of q^(1/2) for links with an odd number of components, but it just multiplies the answer by q^(1/2) to get rid of them. Moroever, the choice of value for the unlink is a little screwy, essentially:: (-q^(1/2) - q^(-1/2))^(n - 1). In the new convention, powers of q^(1/2) never appear, i.e. the new q is the old q^(1/2) and moreover the value for an n-component unlink is (q + 1/q)^(n - 1). This should match Bar-Natan's paper on Khovanov homology. """ if not variable: L = LaurentPolynomialRing(QQ, 'q') variable = L.gen() answer = 0 L_A = LaurentPolynomialRing(QQ, 'A') A = L_A.gen() G = K.white_graph() for i, labels in enumerate(G.edge_labels()): labels['edge_index'] = i writhe = K.writhe() for T in spanning_trees(G): answer = answer + _Jones_contrib(K, G, T, A) answer = answer * (-A)**(3 * writhe) ans = 0 for i in range(len(answer.coefficients())): coeff = answer.coefficients()[i] exp = answer.exponents()[i] if new_convention: # Now do the substitution A = i q^(1/2) so A^2 = -q assert exp % 2 == 0 ans = ans + coeff * ((-variable)**(exp // 2)) else: ans = ans + coeff * (variable**(exp // 4)) return ans
def loop_representation(self): r""" Return the map `\pi` from ``self`` to `2 \times 2` matrices over `R[\lambda,\lambda^{-1}]`, where `F` is the fraction field of the base ring of ``self``. Let `AW` be the Askey-Wilson algebra over `R`, and let `F` be the fraction field of `R`. Let `M` be the space of `2 \times 2` matrices over `F[\lambda, \lambda^{-1}]`. Consider the following elements of `M`: .. MATH:: \mathcal{A} = \begin{pmatrix} \lambda & 1 - \lambda^{-1} \\ 0 & \lambda^{-1} \end{pmatrix}, \qquad \mathcal{B} = \begin{pmatrix} \lambda^{-1} & 0 \\ \lambda - 1 & \lambda \end{pmatrix}, \qquad \mathcal{C} = \begin{pmatrix} 1 & \lambda - 1 \\ 1 - \lambda^{-1} & \lambda + \lambda^{-1} - 1 \end{pmatrix}. From Lemma 3.11 of [Terwilliger2011]_, we define a representation `\pi: AW \to M` by .. MATH:: A \mapsto q \mathcal{A} + q^{-1} \mathcal{A}^{-1}, \qquad B \mapsto q \mathcal{B} + q^{-1} \mathcal{B}^{-1}, \qquad C \mapsto q \mathcal{C} + q^{-1} \mathcal{C}^{-1}, .. MATH:: \alpha, \beta, \gamma \mapsto \nu I, where `\nu = (q^2 + q^-2)(\lambda + \lambda^{-1}) + (\lambda + \lambda^{-1})^2`. We call this representation the *loop representation* as it is a representation using the loop group `SL_2(F[\lambda,\lambda^{-1}])`. EXAMPLES:: sage: AW = algebras.AskeyWilson(QQ) sage: q = AW.q() sage: pi = AW.loop_representation() sage: A,B,C,a,b,g = [pi(gen) for gen in AW.algebra_generators()] sage: A [ 1/q*lambda^-1 + q*lambda ((-q^2 + 1)/q)*lambda^-1 + ((q^2 - 1)/q)] [ 0 q*lambda^-1 + 1/q*lambda] sage: B [ q*lambda^-1 + 1/q*lambda 0] [((-q^2 + 1)/q) + ((q^2 - 1)/q)*lambda 1/q*lambda^-1 + q*lambda] sage: C [1/q*lambda^-1 + ((q^2 - 1)/q) + 1/q*lambda ((q^2 - 1)/q) + ((-q^2 + 1)/q)*lambda] [ ((q^2 - 1)/q)*lambda^-1 + ((-q^2 + 1)/q) q*lambda^-1 + ((-q^2 + 1)/q) + q*lambda] sage: a [lambda^-2 + ((q^4 + 1)/q^2)*lambda^-1 + 2 + ((q^4 + 1)/q^2)*lambda + lambda^2 0] [ 0 lambda^-2 + ((q^4 + 1)/q^2)*lambda^-1 + 2 + ((q^4 + 1)/q^2)*lambda + lambda^2] sage: a == b True sage: a == g True sage: AW.an_element() (q^-3+3+2*q+q^2)*a*b*g^3 + q*A*C^2*b + 3*q^2*B*a^2*g + A sage: x = pi(AW.an_element()) sage: y = (q^-3+3+2*q+q^2)*a*b*g^3 + q*A*C^2*b + 3*q^2*B*a^2*g + A sage: x == y True We check the defining relations of the Askey-Wilson algebra:: sage: A + (q*B*C - q^-1*C*B) / (q^2 - q^-2) == a / (q + q^-1) True sage: B + (q*C*A - q^-1*A*C) / (q^2 - q^-2) == b / (q + q^-1) True sage: C + (q*A*B - q^-1*B*A) / (q^2 - q^-2) == g / (q + q^-1) True We check Lemma 3.12 in [Terwilliger2011]_:: sage: M = pi.codomain() sage: la = M.base_ring().gen() sage: p = M([[0,-1],[1,1]]) sage: s = M([[0,1],[la,0]]) sage: rho = AW.rho() sage: sigma = AW.sigma() sage: all(p*pi(gen)*~p == pi(rho(gen)) for gen in AW.algebra_generators()) True sage: all(s*pi(gen)*~s == pi(sigma(gen)) for gen in AW.algebra_generators()) True """ from sage.matrix.matrix_space import MatrixSpace q = self._q base = LaurentPolynomialRing(self.base_ring().fraction_field(), 'lambda') la = base.gen() inv = ~la M = MatrixSpace(base, 2) A = M([[la,1-inv],[0,inv]]) Ai = M([[inv,inv-1],[0,la]]) B = M([[inv,0],[la-1,la]]) Bi = M([[la,0],[1-la,inv]]) C = M([[1,1-la],[inv-1,la+inv-1]]) Ci = M([[la+inv-1,la-1],[1-inv,1]]) mu = la + inv nu = (self._q**2 + self._q**-2) * mu + mu**2 nuI = M(nu) category = Algebras(Rings().Commutative()) return AlgebraMorphism(self, [q*A + q**-1*Ai, q*B + q**-1*Bi, q*C + q**-1*Ci, nuI, nuI, nuI], codomain=M, category=category)
class AbstractMSumRing(Parent): def __init__(self, *args): if len(args) == 1 and isinstance(args[0], AbstractMSumRing): self._laurent_polynomial_ring = args[0]._laurent_polynomial_ring self._free_module = args[0]._free_module self._laurent_polynomial_ring_extra_var = args[0]._laurent_polynomial_ring_extra_var else: from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.modules.free_module import FreeModule self._laurent_polynomial_ring = LaurentPolynomialRing(QQ, *args) dim = ZZ(self._laurent_polynomial_ring.ngens()) self._free_module = FreeModule(ZZ, dim) # univariate extension of the polynomial ring # (needed in several algorithms) self._laurent_polynomial_ring_extra_var = self._laurent_polynomial_ring['EXTRA_VAR'] Parent.__init__(self, category=Rings()) def ngens(self): return self.laurent_polynomial_ring().ngens() def free_module(self): return self._free_module def polynomial_ring(self): raise ValueError def polynomial_ring_extra_var(self): raise ValueError def laurent_polynomial_ring(self): return self._laurent_polynomial_ring def laurent_polynomial_ring_extra_var(self): return self._laurent_polynomial_ring_extra_var def with_extra_var(self): return self['EXTRA_VAR'] @cached_method def zero(self): r""" EXAMPLES:: sage: from surface_dynamics.misc.multivariate_generating_series import MultivariateGeneratingSeriesRing sage: M = MultivariateGeneratingSeriesRing('x', 2) sage: M.zero() 0 sage: M.zero().parent() is M True sage: M.zero().is_zero() True """ return self._element_constructor_(QQ.zero()) @cached_method def one(self): r""" EXAMPLES:: sage: from surface_dynamics.misc.multivariate_generating_series import MultivariateGeneratingSeriesRing sage: M = MultivariateGeneratingSeriesRing('x', 2) sage: M.zero() 0 sage: M.zero().parent() is M True sage: M.one().is_one() True """ return self._element_constructor_(QQ.one()) def term(self, num, den): r""" Return the term ``num / den``. INPUT: - ``num`` - a Laurent polynomial - ``den`` - a list of pairs ``(vector, power)`` or a dictionary whose keys are the vectors and the values the powers. The vector ``v = (v_0, v_1, \ldots)`` with power ``n`` corresponds to the factor `(1 - x_0^{v_0} x_1^{v_1} \ldots x_k^{v_k})^n`. EXAMPLES:: sage: from surface_dynamics.misc.multivariate_generating_series import MultivariateGeneratingSeriesRing sage: M = MultivariateGeneratingSeriesRing('x', 3) sage: M.term(1, [([1,1,0],1),([1,0,-1],2)]) (1)/((1 - x0*x2^-1)^2*(1 - x0*x1)) sage: M.term(1, {(1,1,0): 1, (1,0,-1): 2}) (1)/((1 - x0*x2^-1)^2*(1 - x0*x1)) """ return self.element_class(self, [(den, num)]) def _element_constructor_(self, arg): r""" TESTS:: sage: from surface_dynamics.misc.multivariate_generating_series import MultivariateGeneratingSeriesRing sage: M = MultivariateGeneratingSeriesRing('x', 2) sage: M(1) (1) sage: R = M.laurent_polynomial_ring() sage: M(R.0) (x0) """ num = self._laurent_polynomial_ring(arg) return self.element_class(self, [([], num)])
def alexander_polynomial(self, multivar=True, v='no', method='default', norm=True, factored=False): """ Calculates the Alexander polynomial of the link. For links with one component, can evaluate the alexander polynomial at v:: sage: K = Link('4_1') sage: K.alexander_polynomial() t^2 - 3*t + 1 sage: K.alexander_polynomial(v=[4]) 5 sage: K = Link('L7n1') sage: K.alexander_polynomial(norm=False) t2^-1 + t1^-1*t2^-4 The default algorithm for *knots* is Bar-Natan's super-fast tangle-based algorithm. For links, we apply Fox calculus to a Wirtinger presentation for the link:: sage: L = Link('K13n123') sage: L.alexander_polynomial() == L.alexander_polynomial(method='wirtinger') True """ # sign normalization still missing, but when "norm=True" the # leading coefficient with respect to the first variable is made # positive. if method == 'snappy': try: return self.exterior().alexander_polynomial() except ImportError: raise RuntimeError('this method for alexander_polynomial ' + no_snappy_msg) else: comp = len(self.link_components) if comp < 2: multivar = False # If single variable, use the super-fast method of Bar-Natan. if comp == 1 and method == 'default' and norm: p = alexander.alexander(self) else: # Use a simple method based on the Wirtinger presentation. if method not in ['default', 'wirtinger']: raise ValueError( "Available methods are 'default' and 'wirtinger'") if (multivar): L = LaurentPolynomialRing( QQ, ['t%d' % (i + 1) for i in range(comp)]) t = list(L.gens()) else: L = LaurentPolynomialRing(QQ, 't') t = [L.gen()] M = self.alexander_matrix(mv=multivar) C = M[0] m = C.nrows() n = C.ncols() if n > m: k = m - 1 else: k = n - 1 subMatrix = C[0:k, 0:k] p = subMatrix.determinant() if p == 0: return 0 if multivar: t_i = M[1][-1] p = (p.factor()) / (t_i - 1) p = p.expand() if (norm): p = normalize_alex_poly(p, t) if v != 'no': return p(*v) if multivar and factored: # it's easier to view this way return p.factor() else: return p
def _LKB_matrix_(self, braid, variab): """ Compute the Lawrence-Krammer-Bigelow representation matrix. The variables of the matrix must be given. This actual computation is done in this helper method for caching purposes. INPUT: - ``braid`` -- tuple of integers. The Tietze list of the braid. - ``variab`` -- string. the names of the variables that will appear in the matrix. They must be given as a string, separated by a comma OUTPUT: The LKB matrix of the braid, with respect to the variables. TESTS:: sage: B=BraidGroup(3) sage: B._LKB_matrix_((2, 1, 2), 'x, y') [ 0 -x^4*y + x^3*y -x^4*y] [ 0 -x^3*y 0] [ -x^2*y x^3*y - x^2*y 0] sage: B._LKB_matrix_((1, 2, 1), 'x, y') [ 0 -x^4*y + x^3*y -x^4*y] [ 0 -x^3*y 0] [ -x^2*y x^3*y - x^2*y 0] sage: B._LKB_matrix_((-1, -2, -1, 2, 1, 2), 'x, y') [1 0 0] [0 1 0] [0 0 1] """ n = self.strands() if len(braid)>1: A = self._LKB_matrix_(braid[:1], variab) for i in braid[1:]: A = A*self._LKB_matrix_((i,), variab) return A l = list(Set(range(n)).subsets(2)) R = LaurentPolynomialRing(IntegerRing(), variab) q = R.gens()[0] t = R.gens()[1] if len(braid)==0: return identity_matrix(R, len(l), sparse=True) A = matrix(R, len(l), sparse=True) if braid[0]>0: i = braid[0]-1 for m in range(len(l)): j = min(l[m]) k = max(l[m]) if i==j-1: A[l.index(Set([i, k])), m] = q A[l.index(Set([i, j])), m] = q*q-q A[l.index(Set([j, k])), m] = 1-q elif i==j and not j==k-1: A[l.index(Set([j, k])), m] = 0 A[l.index(Set([j+1, k])), m] = 1 elif k-1==i and not k-1==j: A[l.index(Set([j, i])), m] = q A[l.index(Set([j, k])), m] = 1-q A[l.index(Set([i, k])), m] = (1-q)*q*t elif i==k: A[l.index(Set([j, k])), m] = 0 A[l.index(Set([j, k+1])), m] = 1 elif i==j and j==k-1: A[l.index(Set([j, k])), m] = -t*q*q else: A[l.index(Set([j, k])), m] = 1 return A else: i = -braid[0]-1 for m in range(len(l)): j = min(l[m]) k = max(l[m]) if i==j-1: A[l.index(Set([j-1, k])), m] = 1 elif i==j and not j==k-1: A[l.index(Set([j+1, k])), m] = q**(-1) A[l.index(Set([j, k])), m] = 1-q**(-1) A[l.index(Set([j, j+1])), m] = t**(-1)*q**(-1)-t**(-1)*q**(-2) elif k-1==i and not k-1==j: A[l.index(Set([j, k-1])), m] = 1 elif i==k: A[l.index(Set([j, k+1])), m] = q**(-1) A[l.index(Set([j, k])), m] = 1-q**(-1) A[l.index(Set([k, k+1])), m] = -q**(-1)+q**(-2) elif i==j and j==k-1: A[l.index(Set([j, k])), m] = -t**(-1)*q**(-2) else: A[l.index(Set([j, k])), m] = 1 return A
def __init__(self, data, **kwargs): r""" See :class:`ClusterAlgebra` for full documentation. """ # TODO: right now we use ClusterQuiver to parse input data. It looks like a good idea but we should make sure it is. # TODO: in base replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented # Temporary variables Q = ClusterQuiver(data) n = Q.n() B0 = Q.b_matrix()[:n,:] I = identity_matrix(n) if 'principal_coefficients' in kwargs and kwargs['principal_coefficients']: M0 = I else: M0 = Q.b_matrix()[n:,:] m = M0.nrows() # Ambient space for F-polynomials # NOTE: for speed purposes we need to have QQ here instead of the more natural ZZ. The reason is that _mutated_F is faster if we do not cast the result to polynomials but then we get "rational" coefficients self._U = PolynomialRing(QQ, ['u%s'%i for i in xrange(n)]) # Storage for computed data self._path_dict = dict([ (v, []) for v in map(tuple,I.columns()) ]) self._F_poly_dict = dict([ (v, self._U(1)) for v in self._path_dict ]) # Determine the names of the initial cluster variables if 'cluster_variables_names' in kwargs: if len(kwargs['cluster_variables_names']) == n: variables = kwargs['cluster_variables_names'] cluster_variables_prefix='dummy' # this is just to avoid checking again if cluster_variables_prefix is defined. Make this better before going public else: raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) else: try: cluster_variables_prefix = kwargs['cluster_variables_prefix'] except: cluster_variables_prefix = 'x' variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] # why not just put str(i) instead of '%s'%i? # Determine scalars try: scalars = kwargs['scalars'] except: scalars = ZZ # Determine coefficients and setup self._base if m>0: if 'coefficients_names' in kwargs: if len(kwargs['coefficients_names']) == m: coefficients = kwargs['coefficients_names'] else: raise ValueError("coefficients_names should be a list of %d valid variable names"%m) else: try: coefficients_prefix = kwargs['coefficients_prefix'] except: coefficients_prefix = 'y' if coefficients_prefix == cluster_variables_prefix: offset = n else: offset = 0 coefficients = [coefficients_prefix+'%s'%i for i in xrange(offset,m+offset)] # TODO: (***) base should eventually become the group algebra of a tropical semifield base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars # TODO: next line should be removed when (***) is implemented coefficients = [] # setup Parent and ambient # TODO: (***) _ambient should eventually be replaced with LaurentPolynomialRing(base, variables) self._ambient = LaurentPolynomialRing(scalars, variables+coefficients) self._ambient_field = self._ambient.fraction_field() # TODO: understand why using Algebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) #self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) # Have we principal coefficients? self._is_principal = (M0 == I) # Store initial data self._B0 = copy(B0) self._n = n self.reset_current_seed() # Internal data for exploring the exchange graph self.reset_exploring_iterator() # Internal data to store exchange relations # This is a dictionary indexed by a frozen set of two g-vectors (the g-vectors of the exchanged variables) # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). # Each of them contains two things # 1) a list of pairs (g-vector, exponent) one for each cluster variable appearing in the term # 2) the coefficient part of the term # TODO: possibly refactor this producing a class ExchangeRelation with some pretty printing feature self._exchange_relations = dict() if 'store_exchange_relations' in kwargs and kwargs['store_exchange_relations']: self._store_exchange_relations = True else: self._store_exchange_relations = False # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__)
def Omega_ge(a, exponents): r""" Return `\Omega_{\ge}` of the expression specified by the input. To be more precise, calculate .. MATH:: \Omega_{\ge} \frac{\mu^a}{ (1 - z_0 \mu^{e_0}) \dots (1 - z_{n-1} \mu^{e_{n-1}})} and return its numerator and a factorization of its denominator. Note that `z_0`, ..., `z_{n-1}` only appear in the output, but not in the input. INPUT: - ``a`` -- an integer - ``exponents`` -- a tuple of integers OUTPUT: A pair representing a quotient as follows: Its first component is the numerator as a Laurent polynomial, its second component a factorization of the denominator as a tuple of Laurent polynomials, where each Laurent polynomial `z` represents a factor `1 - z`. The parents of these Laurent polynomials is always a Laurent polynomial ring in `z_0`, ..., `z_{n-1}` over `\ZZ`, where `n` is the length of ``exponents``. EXAMPLES:: sage: from sage.rings.polynomial.omega import Omega_ge sage: Omega_ge(0, (1, -2)) (1, (z0, z0^2*z1)) sage: Omega_ge(0, (1, -3)) (1, (z0, z0^3*z1)) sage: Omega_ge(0, (1, -4)) (1, (z0, z0^4*z1)) sage: Omega_ge(0, (2, -1)) (z0*z1 + 1, (z0, z0*z1^2)) sage: Omega_ge(0, (3, -1)) (z0*z1^2 + z0*z1 + 1, (z0, z0*z1^3)) sage: Omega_ge(0, (4, -1)) (z0*z1^3 + z0*z1^2 + z0*z1 + 1, (z0, z0*z1^4)) sage: Omega_ge(0, (1, 1, -2)) (-z0^2*z1*z2 - z0*z1^2*z2 + z0*z1*z2 + 1, (z0, z1, z0^2*z2, z1^2*z2)) sage: Omega_ge(0, (2, -1, -1)) (z0*z1*z2 + z0*z1 + z0*z2 + 1, (z0, z0*z1^2, z0*z2^2)) sage: Omega_ge(0, (2, 1, -1)) (-z0*z1*z2^2 - z0*z1*z2 + z0*z2 + 1, (z0, z1, z0*z2^2, z1*z2)) :: sage: Omega_ge(0, (2, -2)) (-z0*z1 + 1, (z0, z0*z1, z0*z1)) sage: Omega_ge(0, (2, -3)) (z0^2*z1 + 1, (z0, z0^3*z1^2)) sage: Omega_ge(0, (3, 1, -3)) (-z0^3*z1^3*z2^3 + 2*z0^2*z1^3*z2^2 - z0*z1^3*z2 + z0^2*z2^2 - 2*z0*z2 + 1, (z0, z1, z0*z2, z0*z2, z0*z2, z1^3*z2)) :: sage: Omega_ge(0, (3, 6, -1)) (-z0*z1*z2^8 - z0*z1*z2^7 - z0*z1*z2^6 - z0*z1*z2^5 - z0*z1*z2^4 + z1*z2^5 - z0*z1*z2^3 + z1*z2^4 - z0*z1*z2^2 + z1*z2^3 - z0*z1*z2 + z0*z2^2 + z1*z2^2 + z0*z2 + z1*z2 + 1, (z0, z1, z0*z2^3, z1*z2^6)) TESTS:: sage: Omega_ge(0, (2, 2, 1, 1, 1, 1, 1, -1, -1))[0].number_of_terms() # long time 27837 :: sage: Omega_ge(1, (2,)) (1, (z0,)) """ import logging logger = logging.getLogger(__name__) logger.info('Omega_ge: a=%s, exponents=%s', a, exponents) from sage.arith.all import lcm, srange from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.number_field.number_field import CyclotomicField if not exponents or any(e == 0 for e in exponents): raise NotImplementedError rou = sorted(set(abs(e) for e in exponents) - set([1])) ellcm = lcm(rou) B = CyclotomicField(ellcm, 'zeta') zeta = B.gen() z_names = tuple('z{}'.format(i) for i in range(len(exponents))) L = LaurentPolynomialRing(B, ('t',) + z_names, len(z_names) + 1) t = L.gens()[0] Z = LaurentPolynomialRing(ZZ, z_names, len(z_names)) powers = {i: L(zeta**(ellcm//i)) for i in rou} powers[2] = L(-1) powers[1] = L(1) exponents_and_values = tuple( (e, tuple(powers[abs(e)]**j * z for j in srange(abs(e)))) for z, e in zip(L.gens()[1:], exponents)) x = tuple(v for e, v in exponents_and_values if e > 0) y = tuple(v for e, v in exponents_and_values if e < 0) def subs_power(expression, var, exponent): r""" Substitute ``var^exponent`` by ``var`` in ``expression``. It is assumed that ``var`` only occurs with exponents divisible by ``exponent``. """ p = tuple(var.dict().popitem()[0]).index(1) # var is the p-th generator def subs_e(e): e = list(e) assert e[p] % exponent == 0 e[p] = e[p] // exponent return tuple(e) parent = expression.parent() result = parent({subs_e(e): c for e, c in iteritems(expression.dict())}) return result def de_power(expression): expression = Z(expression) for e, var in zip(exponents, Z.gens()): if abs(e) == 1: continue expression = subs_power(expression, var, abs(e)) return expression logger.debug('Omega_ge: preparing denominator') factors_denominator = tuple(de_power(1 - factor) for factor in _Omega_factors_denominator_(x, y)) logger.debug('Omega_ge: preparing numerator') numerator = de_power(_Omega_numerator_(a, x, y, t)) logger.info('Omega_ge: completed') return numerator, factors_denominator
def alexander_polynomial(self, var='t', normalized=True): r""" Return the Alexander polynomial of the closure of the braid. INPUT: - ``var`` -- string (default: ``'t'``); the name of the variable in the entries of the matrix - ``normalized`` -- boolean (default: ``True``); whether to return the normalized Alexander polynomial OUTPUT: The Alexander polynomial of the braid closure of the braid. This is computed using the reduced Burau representation. The unnormalized Alexander polynomial is a Laurent polynomial, which is only well-defined up to multiplication by plus or minus times a power of `t`. We normalize the polynomial by dividing by the largest power of `t` and then if the resulting constant coefficient is negative, we multiply by `-1`. EXAMPLES: We first construct the trefoil:: sage: B = BraidGroup(3) sage: b = B([1,2,1,2]) sage: b.alexander_polynomial(normalized=False) 1 - t + t^2 sage: b.alexander_polynomial() t^-2 - t^-1 + 1 Next we construct the figure 8 knot:: sage: b = B([-1,2,-1,2]) sage: b.alexander_polynomial(normalized=False) -t^-2 + 3*t^-1 - 1 sage: b.alexander_polynomial() t^-2 - 3*t^-1 + 1 Our last example is the Kinoshita-Terasaka knot:: sage: B = BraidGroup(4) sage: b = B([1,1,1,3,3,2,-3,-1,-1,2,-1,-3,-2]) sage: b.alexander_polynomial(normalized=False) -t^-1 sage: b.alexander_polynomial() 1 REFERENCES: - :wikipedia:`Alexander_polynomial` """ n = self.strands() p = (self.burau_matrix(reduced=True) - identity_matrix(n - 1)).det() K, t = LaurentPolynomialRing(IntegerRing(), var).objgen() if p == 0: return K.zero() qn = sum(t ** i for i in range(n)) p //= qn if normalized: p *= t ** (-p.degree()) if p.constant_coefficient() < 0: p = -p return p
def burau_matrix(self, var='t', reduced=False): """ Return the Burau matrix of the braid. INPUT: - ``var`` -- string (default: ``'t'``); the name of the variable in the entries of the matrix - ``reduced`` -- boolean (default: ``False``); whether to return the reduced or unreduced Burau representation OUTPUT: The Burau matrix of the braid. It is a matrix whose entries are Laurent polynomials in the variable ``var``. If ``reduced`` is ``True``, return the matrix for the reduced Burau representation instead. EXAMPLES:: sage: B = BraidGroup(4) sage: B.inject_variables() Defining s0, s1, s2 sage: b = s0*s1/s2/s1 sage: b.burau_matrix() [ 1 - t 0 t - t^2 t^2] [ 1 0 0 0] [ 0 0 1 0] [ 0 t^-2 -t^-2 + t^-1 -t^-1 + 1] sage: s2.burau_matrix('x') [ 1 0 0 0] [ 0 1 0 0] [ 0 0 1 - x x] [ 0 0 1 0] sage: s0.burau_matrix(reduced=True) [-t 0 0] [-t 1 0] [-t 0 1] REFERENCES: - :wikipedia:`Burau_representation` """ R = LaurentPolynomialRing(IntegerRing(), var) t = R.gen() n = self.strands() if not reduced: M = identity_matrix(R, n) for i in self.Tietze(): A = identity_matrix(R, n) if i > 0: A[i-1, i-1] = 1-t A[i, i] = 0 A[i, i-1] = 1 A[i-1, i] = t if i < 0: A[-1-i, -1-i] = 0 A[-i, -i] = 1-t**(-1) A[-1-i, -i] = 1 A[-i, -1-i] = t**(-1) M = M * A else: M = identity_matrix(R, n - 1) for j in self.Tietze(): A = identity_matrix(R, n - 1) if j > 1: i = j-1 A[i-1, i-1] = 1-t A[i, i] = 0 A[i, i-1] = 1 A[i-1, i] = t if j < -1: i = j+1 A[-1-i, -1-i] = 0 A[-i, -i] = 1-t**(-1) A[-1-i, -i] = 1 A[-i, -1-i] = t**(-1) if j == 1: for k in range(n - 1): A[k,0] = -t if j == -1: A[0,0] = -t**(-1) for k in range(1, n - 1): A[k,0] = -1 M = M * A return M
def MacMahonOmega(var, expression, denominator=None, op=operator.ge, Factorization_sort=False, Factorization_simplify=True): r""" Return `\Omega_{\mathrm{op}}` of ``expression`` with respect to ``var``. To be more precise, calculate .. MATH:: \Omega_{\mathrm{op}} \frac{n}{d_1 \dots d_n} for the numerator `n` and the factors `d_1`, ..., `d_n` of the denominator, all of which are Laurent polynomials in ``var`` and return a (partial) factorization of the result. INPUT: - ``var`` -- a variable or a representation string of a variable - ``expression`` -- a :class:`~sage.structure.factorization.Factorization` of Laurent polynomials or, if ``denominator`` is specified, a Laurent polynomial interpreted as the numerator of the expression - ``denominator`` -- a Laurent polynomial or a :class:`~sage.structure.factorization.Factorization` (consisting of Laurent polynomial factors) or a tuple/list of factors (Laurent polynomials) - ``op`` -- (default: ``operator.ge``) an operator At the moment only ``operator.ge`` is implemented. - ``Factorization_sort`` (default: ``False``) and ``Factorization_simplify`` (default: ``True``) -- are passed on to :class:`sage.structure.factorization.Factorization` when creating the result OUTPUT: A (partial) :class:`~sage.structure.factorization.Factorization` of the result whose factors are Laurent polynomials .. NOTE:: The numerator of the result may not be factored. REFERENCES: - [Mac1915]_ - [APR2001]_ EXAMPLES:: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(ZZ) sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu, 1 - z/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 * (-x*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu]) (-x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^2]) 1 * (-x + 1)^-1 * (-x^2*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu]) (x*y + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu^2]) (-x^2*y*z - x*y^2*z + x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x^2*z + 1)^-1 * (-y^2*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^3]) 1 * (-x + 1)^-1 * (-x^3*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^4]) 1 * (-x + 1)^-1 * (-x^4*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^3, 1 - y/mu]) (x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^3 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^4, 1 - y/mu]) (x*y^3 + x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^4 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu, 1 - z/mu]) (x*y*z + x*y + x*z + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 * (-x*z^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y*mu, 1 - z/mu]) (-x*y*z^2 - x*y*z + x*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z^2 + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z*mu, 1 - w/mu]) (x*y*z*w^2 + x*y*z*w - x*y*w - x*z*w - y*z*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-z + 1)^-1 * (-x*w + 1)^-1 * (-y*w + 1)^-1 * (-z*w + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu, 1 - w/mu]) (x^2*y*z*w + x*y^2*z*w - x*y*z*w - x*y*z - x*y*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-x*w + 1)^-1 * (-y*z + 1)^-1 * (-y*w + 1)^-1 sage: MacMahonOmega(mu, mu^-2, [1 - x*mu, 1 - y/mu]) x^2 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^-1, [1 - x*mu, 1 - y/mu]) x * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu, [1 - x*mu, 1 - y/mu]) (-x*y + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 We demonstrate the different allowed input variants:: sage: MacMahonOmega(mu, ....: Factorization([(mu, 2), (1 - x*mu, -1), (1 - y/mu, -1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, (1 - x*mu)*(1 - y/mu)) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2 / ((1 - x*mu)*(1 - y/mu))) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 TESTS:: sage: MacMahonOmega(mu, 1, [1 - x*mu]) 1 * (-x + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x/mu]) 1 sage: MacMahonOmega(mu, 0, [1 - x*mu]) 0 sage: MacMahonOmega(mu, L(1), []) 1 sage: MacMahonOmega(mu, L(0), []) 0 sage: MacMahonOmega(mu, 2, []) 2 sage: MacMahonOmega(mu, 2*mu, []) 2 sage: MacMahonOmega(mu, 2/mu, []) 0 :: sage: MacMahonOmega(mu, Factorization([(1/mu, 1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x, -1)])) 0 sage: MacMahonOmega(mu, Factorization([(2, -1)])) 1 * 2^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - z, 1 - y/mu]) 1 * (-z + 1)^-1 * (-x + 1)^-1 * (-x*y + 1)^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu], op=operator.lt) Traceback (most recent call last): ... NotImplementedError: At the moment, only Omega_ge is implemented. sage: MacMahonOmega(mu, 1, Factorization([(1 - x*mu, -1)])) Traceback (most recent call last): ... ValueError: Factorization (-mu*x + 1)^-1 of the denominator contains negative exponents. sage: MacMahonOmega(2*mu, 1, [1 - x*mu]) Traceback (most recent call last): ... ValueError: 2*mu is not a variable. sage: MacMahonOmega(mu, 1, Factorization([(0, 2)])) Traceback (most recent call last): ... ZeroDivisionError: Denominator contains a factor 0. sage: MacMahonOmega(mu, 1, [2 - x*mu]) Traceback (most recent call last): ... NotImplementedError: Factor 2 - x*mu is not normalized. sage: MacMahonOmega(mu, 1, [1 - x*mu - mu^2]) Traceback (most recent call last): ... NotImplementedError: Cannot handle factor 1 - x*mu - mu^2. :: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(QQ) sage: MacMahonOmega(mu, 1/mu, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 2)], unit=2)) 1/2*x * (-x + 1)^-1 * (-x*y + 1)^-2 """ from sage.arith.misc import factor from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring \ import LaurentPolynomialRing, LaurentPolynomialRing_univariate from sage.structure.factorization import Factorization if op != operator.ge: raise NotImplementedError('At the moment, only Omega_ge is implemented.') if denominator is None: if isinstance(expression, Factorization): numerator = expression.unit() * \ prod(f**e for f, e in expression if e > 0) denominator = tuple(f for f, e in expression if e < 0 for _ in range(-e)) else: numerator = expression.numerator() denominator = expression.denominator() else: numerator = expression # at this point we have numerator/denominator if isinstance(denominator, (list, tuple)): factors_denominator = denominator else: if not isinstance(denominator, Factorization): denominator = factor(denominator) if not denominator.is_integral(): raise ValueError('Factorization {} of the denominator ' 'contains negative exponents.'.format(denominator)) numerator *= ZZ(1) / denominator.unit() factors_denominator = tuple(factor for factor, exponent in denominator for _ in range(exponent)) # at this point we have numerator/factors_denominator P = var.parent() if isinstance(P, LaurentPolynomialRing_univariate) and P.gen() == var: L = P L0 = L.base_ring() elif var in P.gens(): var = repr(var) L0 = LaurentPolynomialRing( P.base_ring(), tuple(v for v in P.variable_names() if v != var)) L = LaurentPolynomialRing(L0, var) var = L.gen() else: raise ValueError('{} is not a variable.'.format(var)) other_factors = [] to_numerator = [] decoded_factors = [] for factor in factors_denominator: factor = L(factor) D = factor.dict() if not D: raise ZeroDivisionError('Denominator contains a factor 0.') elif len(D) == 1: exponent, coefficient = next(iteritems(D)) if exponent == 0: other_factors.append(L0(factor)) else: to_numerator.append(factor) elif len(D) == 2: if D.get(0, 0) != 1: raise NotImplementedError('Factor {} is not normalized.'.format(factor)) D.pop(0) exponent, coefficient = next(iteritems(D)) decoded_factors.append((-coefficient, exponent)) else: raise NotImplementedError('Cannot handle factor {}.'.format(factor)) numerator = L(numerator) / prod(to_numerator) result_numerator, result_factors_denominator = \ _Omega_(numerator.dict(), decoded_factors) if result_numerator == 0: return Factorization([], unit=result_numerator) return Factorization([(result_numerator, 1)] + list((f, -1) for f in other_factors) + list((1-f, -1) for f in result_factors_denominator), sort=Factorization_sort, simplify=Factorization_simplify)
def Omega_ge(a, exponents): r""" Return `\Omega_{\ge}` of the expression specified by the input. To be more precise, calculate .. MATH:: \Omega_{\ge} \frac{\mu^a}{ (1 - z_0 \mu^{e_0}) \dots (1 - z_{n-1} \mu^{e_{n-1}})} and return its numerator and a factorization of its denominator. Note that `z_0`, ..., `z_{n-1}` only appear in the output, but not in the input. INPUT: - ``a`` -- an integer - ``exponents`` -- a tuple of integers OUTPUT: A pair representing a quotient as follows: Its first component is the numerator as a Laurent polynomial, its second component a factorization of the denominator as a tuple of Laurent polynomials, where each Laurent polynomial `z` represents a factor `1 - z`. The parents of these Laurent polynomials is always a Laurent polynomial ring in `z_0`, ..., `z_{n-1}` over `\ZZ`, where `n` is the length of ``exponents``. EXAMPLES:: sage: from sage.rings.polynomial.omega import Omega_ge sage: Omega_ge(0, (1, -2)) (1, (z0, z0^2*z1)) sage: Omega_ge(0, (1, -3)) (1, (z0, z0^3*z1)) sage: Omega_ge(0, (1, -4)) (1, (z0, z0^4*z1)) sage: Omega_ge(0, (2, -1)) (z0*z1 + 1, (z0, z0*z1^2)) sage: Omega_ge(0, (3, -1)) (z0*z1^2 + z0*z1 + 1, (z0, z0*z1^3)) sage: Omega_ge(0, (4, -1)) (z0*z1^3 + z0*z1^2 + z0*z1 + 1, (z0, z0*z1^4)) sage: Omega_ge(0, (1, 1, -2)) (-z0^2*z1*z2 - z0*z1^2*z2 + z0*z1*z2 + 1, (z0, z1, z0^2*z2, z1^2*z2)) sage: Omega_ge(0, (2, -1, -1)) (z0*z1*z2 + z0*z1 + z0*z2 + 1, (z0, z0*z1^2, z0*z2^2)) sage: Omega_ge(0, (2, 1, -1)) (-z0*z1*z2^2 - z0*z1*z2 + z0*z2 + 1, (z0, z1, z0*z2^2, z1*z2)) :: sage: Omega_ge(0, (2, -2)) (-z0*z1 + 1, (z0, z0*z1, z0*z1)) sage: Omega_ge(0, (2, -3)) (z0^2*z1 + 1, (z0, z0^3*z1^2)) sage: Omega_ge(0, (3, 1, -3)) (-z0^3*z1^3*z2^3 + 2*z0^2*z1^3*z2^2 - z0*z1^3*z2 + z0^2*z2^2 - 2*z0*z2 + 1, (z0, z1, z0*z2, z0*z2, z0*z2, z1^3*z2)) :: sage: Omega_ge(0, (3, 6, -1)) (-z0*z1*z2^8 - z0*z1*z2^7 - z0*z1*z2^6 - z0*z1*z2^5 - z0*z1*z2^4 + z1*z2^5 - z0*z1*z2^3 + z1*z2^4 - z0*z1*z2^2 + z1*z2^3 - z0*z1*z2 + z0*z2^2 + z1*z2^2 + z0*z2 + z1*z2 + 1, (z0, z1, z0*z2^3, z1*z2^6)) TESTS:: sage: Omega_ge(0, (2, 2, 1, 1, 1, -1, -1))[0].number_of_terms() # long time 1695 sage: Omega_ge(0, (2, 2, 1, 1, 1, 1, 1, -1, -1))[0].number_of_terms() # not tested (too long, 1 min) 27837 :: sage: Omega_ge(1, (2,)) (1, (z0,)) """ import logging logger = logging.getLogger(__name__) logger.info('Omega_ge: a=%s, exponents=%s', a, exponents) from sage.arith.all import lcm, srange from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.number_field.number_field import CyclotomicField if not exponents or any(e == 0 for e in exponents): raise NotImplementedError rou = sorted(set(abs(e) for e in exponents) - set([1])) ellcm = lcm(rou) B = CyclotomicField(ellcm, 'zeta') zeta = B.gen() z_names = tuple('z{}'.format(i) for i in range(len(exponents))) L = LaurentPolynomialRing(B, ('t', ) + z_names, len(z_names) + 1) t = L.gens()[0] Z = LaurentPolynomialRing(ZZ, z_names, len(z_names)) powers = {i: L(zeta**(ellcm // i)) for i in rou} powers[2] = L(-1) powers[1] = L(1) exponents_and_values = tuple( (e, tuple(powers[abs(e)]**j * z for j in srange(abs(e)))) for z, e in zip(L.gens()[1:], exponents)) x = tuple(v for e, v in exponents_and_values if e > 0) y = tuple(v for e, v in exponents_and_values if e < 0) def subs_power(expression, var, exponent): r""" Substitute ``var^exponent`` by ``var`` in ``expression``. It is assumed that ``var`` only occurs with exponents divisible by ``exponent``. """ p = tuple(var.dict().popitem()[0]).index( 1) # var is the p-th generator def subs_e(e): e = list(e) assert e[p] % exponent == 0 e[p] = e[p] // exponent return tuple(e) parent = expression.parent() result = parent( {subs_e(e): c for e, c in iteritems(expression.dict())}) return result def de_power(expression): expression = Z(expression) for e, var in zip(exponents, Z.gens()): if abs(e) == 1: continue expression = subs_power(expression, var, abs(e)) return expression logger.debug('Omega_ge: preparing denominator') factors_denominator = tuple( de_power(1 - factor) for factor in _Omega_factors_denominator_(x, y)) logger.debug('Omega_ge: preparing numerator') numerator = de_power(_Omega_numerator_(a, x, y, t)) logger.info('Omega_ge: completed') return numerator, factors_denominator
def loop_representation(self): r""" Return the map `\pi` from ``self`` to `2 \times 2` matrices over `R[\lambda,\lambda^{-1}]`, where `F` is the fraction field of the base ring of ``self``. Let `AW` be the Askey-Wilson algebra over `R`, and let `F` be the fraction field of `R`. Let `M` be the space of `2 \times 2` matrices over `F[\lambda, \lambda^{-1}]`. Consider the following elements of `M`: .. MATH:: \mathcal{A} = \begin{pmatrix} \lambda & 1 - \lambda^{-1} \\ 0 & \lambda^{-1} \end{pmatrix}, \qquad \mathcal{B} = \begin{pmatrix} \lambda^{-1} & 0 \\ \lambda - 1 & \lambda \end{pmatrix}, \qquad \mathcal{C} = \begin{pmatrix} 1 & \lambda - 1 \\ 1 - \lambda^{-1} & \lambda + \lambda^{-1} - 1 \end{pmatrix}. From Lemma 3.11 of [Terwilliger2011]_, we define a representation `\pi: AW \to M` by .. MATH:: A \mapsto q \mathcal{A} + q^{-1} \mathcal{A}^{-1}, \qquad B \mapsto q \mathcal{B} + q^{-1} \mathcal{B}^{-1}, \qquad C \mapsto q \mathcal{C} + q^{-1} \mathcal{C}^{-1}, .. MATH:: \alpha, \beta, \gamma \mapsto \nu I, where `\nu = (q^2 + q^-2)(\lambda + \lambda^{-1}) + (\lambda + \lambda^{-1})^2`. We call this representation the *loop representation* as it is a representation using the loop group `SL_2(F[\lambda,\lambda^{-1}])`. EXAMPLES:: sage: AW = algebras.AskeyWilson(QQ) sage: q = AW.q() sage: pi = AW.loop_representation() sage: A,B,C,a,b,g = [pi(gen) for gen in AW.algebra_generators()] sage: A [ 1/q*lambda^-1 + q*lambda ((-q^2 + 1)/q)*lambda^-1 + ((q^2 - 1)/q)] [ 0 q*lambda^-1 + 1/q*lambda] sage: B [ q*lambda^-1 + 1/q*lambda 0] [((-q^2 + 1)/q) + ((q^2 - 1)/q)*lambda 1/q*lambda^-1 + q*lambda] sage: C [1/q*lambda^-1 + ((q^2 - 1)/q) + 1/q*lambda ((q^2 - 1)/q) + ((-q^2 + 1)/q)*lambda] [ ((q^2 - 1)/q)*lambda^-1 + ((-q^2 + 1)/q) q*lambda^-1 + ((-q^2 + 1)/q) + q*lambda] sage: a [lambda^-2 + ((q^4 + 1)/q^2)*lambda^-1 + 2 + ((q^4 + 1)/q^2)*lambda + lambda^2 0] [ 0 lambda^-2 + ((q^4 + 1)/q^2)*lambda^-1 + 2 + ((q^4 + 1)/q^2)*lambda + lambda^2] sage: a == b True sage: a == g True sage: AW.an_element() (q^-3+3+2*q+q^2)*a*b*g^3 + q*A*C^2*b + 3*q^2*B*a^2*g + A sage: x = pi(AW.an_element()) sage: y = (q^-3+3+2*q+q^2)*a*b*g^3 + q*A*C^2*b + 3*q^2*B*a^2*g + A sage: x == y True We check the defining relations of the Askey-Wilson algebra:: sage: A + (q*B*C - q^-1*C*B) / (q^2 - q^-2) == a / (q + q^-1) True sage: B + (q*C*A - q^-1*A*C) / (q^2 - q^-2) == b / (q + q^-1) True sage: C + (q*A*B - q^-1*B*A) / (q^2 - q^-2) == g / (q + q^-1) True We check Lemma 3.12 in [Terwilliger2011]_:: sage: M = pi.codomain() sage: la = M.base_ring().gen() sage: p = M([[0,-1],[1,1]]) sage: s = M([[0,1],[la,0]]) sage: rho = AW.rho() sage: sigma = AW.sigma() sage: all(p*pi(gen)*~p == pi(rho(gen)) for gen in AW.algebra_generators()) True sage: all(s*pi(gen)*~s == pi(sigma(gen)) for gen in AW.algebra_generators()) True """ from sage.matrix.matrix_space import MatrixSpace q = self._q base = LaurentPolynomialRing(self.base_ring().fraction_field(), 'lambda') la = base.gen() inv = ~la M = MatrixSpace(base, 2) A = M([[la, 1 - inv], [0, inv]]) Ai = M([[inv, inv - 1], [0, la]]) B = M([[inv, 0], [la - 1, la]]) Bi = M([[la, 0], [1 - la, inv]]) C = M([[1, 1 - la], [inv - 1, la + inv - 1]]) Ci = M([[la + inv - 1, la - 1], [1 - inv, 1]]) mu = la + inv nu = (self._q**2 + self._q**-2) * mu + mu**2 nuI = M(nu) category = Algebras(Rings().Commutative()) return AlgebraMorphism(self, [ q * A + q**-1 * Ai, q * B + q**-1 * Bi, q * C + q**-1 * Ci, nuI, nuI, nuI ], codomain=M, category=category)
def MacMahonOmega(var, expression, denominator=None, op=operator.ge, Factorization_sort=False, Factorization_simplify=True): r""" Return `\Omega_{\mathrm{op}}` of ``expression`` with respect to ``var``. To be more precise, calculate .. MATH:: \Omega_{\mathrm{op}} \frac{n}{d_1 \dots d_n} for the numerator `n` and the factors `d_1`, ..., `d_n` of the denominator, all of which are Laurent polynomials in ``var`` and return a (partial) factorization of the result. INPUT: - ``var`` -- a variable or a representation string of a variable - ``expression`` -- a :class:`~sage.structure.factorization.Factorization` of Laurent polynomials or, if ``denominator`` is specified, a Laurent polynomial interpreted as the numerator of the expression - ``denominator`` -- a Laurent polynomial or a :class:`~sage.structure.factorization.Factorization` (consisting of Laurent polynomial factors) or a tuple/list of factors (Laurent polynomials) - ``op`` -- (default: ``operator.ge``) an operator At the moment only ``operator.ge`` is implemented. - ``Factorization_sort`` (default: ``False``) and ``Factorization_simplify`` (default: ``True``) -- are passed on to :class:`sage.structure.factorization.Factorization` when creating the result OUTPUT: A (partial) :class:`~sage.structure.factorization.Factorization` of the result whose factors are Laurent polynomials .. NOTE:: The numerator of the result may not be factored. REFERENCES: - [Mac1915]_ - [APR2001]_ EXAMPLES:: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(ZZ) sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu, 1 - z/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 * (-x*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu]) (-x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^2]) 1 * (-x + 1)^-1 * (-x^2*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu]) (x*y + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu^2]) (-x^2*y*z - x*y^2*z + x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x^2*z + 1)^-1 * (-y^2*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^3]) 1 * (-x + 1)^-1 * (-x^3*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^4]) 1 * (-x + 1)^-1 * (-x^4*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^3, 1 - y/mu]) (x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^3 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^4, 1 - y/mu]) (x*y^3 + x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^4 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu, 1 - z/mu]) (x*y*z + x*y + x*z + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 * (-x*z^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y*mu, 1 - z/mu]) (-x*y*z^2 - x*y*z + x*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z^2 + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z*mu, 1 - w/mu]) (x*y*z*w^2 + x*y*z*w - x*y*w - x*z*w - y*z*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-z + 1)^-1 * (-x*w + 1)^-1 * (-y*w + 1)^-1 * (-z*w + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu, 1 - w/mu]) (x^2*y*z*w + x*y^2*z*w - x*y*z*w - x*y*z - x*y*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-x*w + 1)^-1 * (-y*z + 1)^-1 * (-y*w + 1)^-1 sage: MacMahonOmega(mu, mu^-2, [1 - x*mu, 1 - y/mu]) x^2 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^-1, [1 - x*mu, 1 - y/mu]) x * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu, [1 - x*mu, 1 - y/mu]) (-x*y + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 We demonstrate the different allowed input variants:: sage: MacMahonOmega(mu, ....: Factorization([(mu, 2), (1 - x*mu, -1), (1 - y/mu, -1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, (1 - x*mu)*(1 - y/mu)) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2 / ((1 - x*mu)*(1 - y/mu))) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 TESTS:: sage: MacMahonOmega(mu, 1, [1 - x*mu]) 1 * (-x + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x/mu]) 1 sage: MacMahonOmega(mu, 0, [1 - x*mu]) 0 sage: MacMahonOmega(mu, L(1), []) 1 sage: MacMahonOmega(mu, L(0), []) 0 sage: MacMahonOmega(mu, 2, []) 2 sage: MacMahonOmega(mu, 2*mu, []) 2 sage: MacMahonOmega(mu, 2/mu, []) 0 :: sage: MacMahonOmega(mu, Factorization([(1/mu, 1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x, -1)])) 0 sage: MacMahonOmega(mu, Factorization([(2, -1)])) 1 * 2^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - z, 1 - y/mu]) 1 * (-z + 1)^-1 * (-x + 1)^-1 * (-x*y + 1)^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu], op=operator.lt) Traceback (most recent call last): ... NotImplementedError: At the moment, only Omega_ge is implemented. sage: MacMahonOmega(mu, 1, Factorization([(1 - x*mu, -1)])) Traceback (most recent call last): ... ValueError: Factorization (-mu*x + 1)^-1 of the denominator contains negative exponents. sage: MacMahonOmega(2*mu, 1, [1 - x*mu]) Traceback (most recent call last): ... ValueError: 2*mu is not a variable. sage: MacMahonOmega(mu, 1, Factorization([(0, 2)])) Traceback (most recent call last): ... ZeroDivisionError: Denominator contains a factor 0. sage: MacMahonOmega(mu, 1, [2 - x*mu]) Traceback (most recent call last): ... NotImplementedError: Factor 2 - x*mu is not normalized. sage: MacMahonOmega(mu, 1, [1 - x*mu - mu^2]) Traceback (most recent call last): ... NotImplementedError: Cannot handle factor 1 - x*mu - mu^2. :: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(QQ) sage: MacMahonOmega(mu, 1/mu, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 2)], unit=2)) 1/2*x * (-x + 1)^-1 * (-x*y + 1)^-2 """ from sage.arith.misc import factor from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring \ import LaurentPolynomialRing, LaurentPolynomialRing_univariate from sage.structure.factorization import Factorization if op != operator.ge: raise NotImplementedError( 'At the moment, only Omega_ge is implemented.') if denominator is None: if isinstance(expression, Factorization): numerator = expression.unit() * \ prod(f**e for f, e in expression if e > 0) denominator = tuple(f for f, e in expression if e < 0 for _ in range(-e)) else: numerator = expression.numerator() denominator = expression.denominator() else: numerator = expression # at this point we have numerator/denominator if isinstance(denominator, (list, tuple)): factors_denominator = denominator else: if not isinstance(denominator, Factorization): denominator = factor(denominator) if not denominator.is_integral(): raise ValueError( 'Factorization {} of the denominator ' 'contains negative exponents.'.format(denominator)) numerator *= ZZ(1) / denominator.unit() factors_denominator = tuple(factor for factor, exponent in denominator for _ in range(exponent)) # at this point we have numerator/factors_denominator P = var.parent() if isinstance(P, LaurentPolynomialRing_univariate) and P.gen() == var: L = P L0 = L.base_ring() elif var in P.gens(): var = repr(var) L0 = LaurentPolynomialRing( P.base_ring(), tuple(v for v in P.variable_names() if v != var)) L = LaurentPolynomialRing(L0, var) var = L.gen() else: raise ValueError('{} is not a variable.'.format(var)) other_factors = [] to_numerator = [] decoded_factors = [] for factor in factors_denominator: factor = L(factor) D = factor.dict() if not D: raise ZeroDivisionError('Denominator contains a factor 0.') elif len(D) == 1: exponent, coefficient = next(iteritems(D)) if exponent == 0: other_factors.append(L0(factor)) else: to_numerator.append(factor) elif len(D) == 2: if D.get(0, 0) != 1: raise NotImplementedError( 'Factor {} is not normalized.'.format(factor)) D.pop(0) exponent, coefficient = next(iteritems(D)) decoded_factors.append((-coefficient, exponent)) else: raise NotImplementedError( 'Cannot handle factor {}.'.format(factor)) numerator = L(numerator) / prod(to_numerator) result_numerator, result_factors_denominator = \ _Omega_(numerator.dict(), decoded_factors) if result_numerator == 0: return Factorization([], unit=result_numerator) return Factorization([(result_numerator, 1)] + list( (f, -1) for f in other_factors) + list( (1 - f, -1) for f in result_factors_denominator), sort=Factorization_sort, simplify=Factorization_simplify)
def __init__(self, data, **kwargs): r""" See :class:`ClusterAlgebra` for full documentation. """ # TODO: right now we use ClusterQuiver to parse input data. It looks like a good idea but we should make sure it is. # TODO: in base replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented # Temporary variables Q = ClusterQuiver(data) n = Q.n() B0 = Q.b_matrix()[:n, :] I = identity_matrix(n) if 'principal_coefficients' in kwargs and kwargs[ 'principal_coefficients']: M0 = I else: M0 = Q.b_matrix()[n:, :] m = M0.nrows() # Ambient space for F-polynomials # NOTE: for speed purposes we need to have QQ here instead of the more natural ZZ. The reason is that _mutated_F is faster if we do not cast the result to polynomials but then we get "rational" coefficients self._U = PolynomialRing(QQ, ['u%s' % i for i in xrange(n)]) # Storage for computed data self._path_dict = dict([(v, []) for v in map(tuple, I.columns())]) self._F_poly_dict = dict([(v, self._U(1)) for v in self._path_dict]) # Determine the names of the initial cluster variables if 'cluster_variables_names' in kwargs: if len(kwargs['cluster_variables_names']) == n: variables = kwargs['cluster_variables_names'] cluster_variables_prefix = 'dummy' # this is just to avoid checking again if cluster_variables_prefix is defined. Make this better before going public else: raise ValueError( "cluster_variables_names should be a list of %d valid variable names" % n) else: try: cluster_variables_prefix = kwargs['cluster_variables_prefix'] except: cluster_variables_prefix = 'x' variables = [ cluster_variables_prefix + '%s' % i for i in xrange(n) ] # why not just put str(i) instead of '%s'%i? # Determine scalars try: scalars = kwargs['scalars'] except: scalars = ZZ # Determine coefficients and setup self._base if m > 0: if 'coefficients_names' in kwargs: if len(kwargs['coefficients_names']) == m: coefficients = kwargs['coefficients_names'] else: raise ValueError( "coefficients_names should be a list of %d valid variable names" % m) else: try: coefficients_prefix = kwargs['coefficients_prefix'] except: coefficients_prefix = 'y' if coefficients_prefix == cluster_variables_prefix: offset = n else: offset = 0 coefficients = [ coefficients_prefix + '%s' % i for i in xrange(offset, m + offset) ] # TODO: (***) base should eventually become the group algebra of a tropical semifield base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars # TODO: next line should be removed when (***) is implemented coefficients = [] # setup Parent and ambient # TODO: (***) _ambient should eventually be replaced with LaurentPolynomialRing(base, variables) self._ambient = LaurentPolynomialRing(scalars, variables + coefficients) self._ambient_field = self._ambient.fraction_field() # TODO: understand why using Algebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables + coefficients) # Data to compute cluster variables using separation of additions # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n + i)**M0[i, j] for i in xrange(m)])) for j in xrange(n) ]) #self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i, j] for i in xrange(n)]) * self._y[self._U.gen(j)]) for j in xrange(n) ]) # Have we principal coefficients? self._is_principal = (M0 == I) # Store initial data self._B0 = copy(B0) self._n = n self.reset_current_seed() # Internal data for exploring the exchange graph self.reset_exploring_iterator() # Internal data to store exchange relations # This is a dictionary indexed by a frozen set of two g-vectors (the g-vectors of the exchanged variables) # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). # Each of them contains two things # 1) a list of pairs (g-vector, exponent) one for each cluster variable appearing in the term # 2) the coefficient part of the term # TODO: possibly refactor this producing a class ExchangeRelation with some pretty printing feature self._exchange_relations = dict() if 'store_exchange_relations' in kwargs and kwargs[ 'store_exchange_relations']: self._store_exchange_relations = True else: self._store_exchange_relations = False # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__)
class ClusterAlgebra(Parent): r""" INPUT: - ``data`` -- some data defining a cluster algebra. - ``scalars`` -- (default ZZ) the scalars on which the cluster algebra is defined. - ``cluster_variables_prefix`` -- string (default 'x'). - ``cluster_variables_names`` -- a list of strings. Superseedes ``cluster_variables_prefix``. - ``coefficients_prefix`` -- string (default 'y'). - ``coefficients_names`` -- a list of strings. Superseedes ``cluster_variables_prefix``. - ``principal_coefficients`` -- bool (default: False). Superseedes any coefficient defined by ``data``. """ Element = ClusterAlgebraElement def __init__(self, data, **kwargs): r""" See :class:`ClusterAlgebra` for full documentation. """ # TODO: right now we use ClusterQuiver to parse input data. It looks like a good idea but we should make sure it is. # TODO: in base replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented # Temporary variables Q = ClusterQuiver(data) n = Q.n() B0 = Q.b_matrix()[:n,:] I = identity_matrix(n) if 'principal_coefficients' in kwargs and kwargs['principal_coefficients']: M0 = I else: M0 = Q.b_matrix()[n:,:] m = M0.nrows() # Ambient space for F-polynomials # NOTE: for speed purposes we need to have QQ here instead of the more natural ZZ. The reason is that _mutated_F is faster if we do not cast the result to polynomials but then we get "rational" coefficients self._U = PolynomialRing(QQ, ['u%s'%i for i in xrange(n)]) # Storage for computed data self._path_dict = dict([ (v, []) for v in map(tuple,I.columns()) ]) self._F_poly_dict = dict([ (v, self._U(1)) for v in self._path_dict ]) # Determine the names of the initial cluster variables if 'cluster_variables_names' in kwargs: if len(kwargs['cluster_variables_names']) == n: variables = kwargs['cluster_variables_names'] cluster_variables_prefix='dummy' # this is just to avoid checking again if cluster_variables_prefix is defined. Make this better before going public else: raise ValueError("cluster_variables_names should be a list of %d valid variable names"%n) else: try: cluster_variables_prefix = kwargs['cluster_variables_prefix'] except: cluster_variables_prefix = 'x' variables = [cluster_variables_prefix+'%s'%i for i in xrange(n)] # why not just put str(i) instead of '%s'%i? # Determine scalars try: scalars = kwargs['scalars'] except: scalars = ZZ # Determine coefficients and setup self._base if m>0: if 'coefficients_names' in kwargs: if len(kwargs['coefficients_names']) == m: coefficients = kwargs['coefficients_names'] else: raise ValueError("coefficients_names should be a list of %d valid variable names"%m) else: try: coefficients_prefix = kwargs['coefficients_prefix'] except: coefficients_prefix = 'y' if coefficients_prefix == cluster_variables_prefix: offset = n else: offset = 0 coefficients = [coefficients_prefix+'%s'%i for i in xrange(offset,m+offset)] # TODO: (***) base should eventually become the group algebra of a tropical semifield base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars # TODO: next line should be removed when (***) is implemented coefficients = [] # setup Parent and ambient # TODO: (***) _ambient should eventually be replaced with LaurentPolynomialRing(base, variables) self._ambient = LaurentPolynomialRing(scalars, variables+coefficients) self._ambient_field = self._ambient.fraction_field() # TODO: understand why using Algebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables+coefficients) # Data to compute cluster variables using separation of additions # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n+i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) #self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i,j] for i in xrange(n)])*self._y[self._U.gen(j)]) for j in xrange(n)]) # Have we principal coefficients? self._is_principal = (M0 == I) # Store initial data self._B0 = copy(B0) self._n = n self.reset_current_seed() # Internal data for exploring the exchange graph self.reset_exploring_iterator() # Internal data to store exchange relations # This is a dictionary indexed by a frozen set of two g-vectors (the g-vectors of the exchanged variables) # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). # Each of them contains two things # 1) a list of pairs (g-vector, exponent) one for each cluster variable appearing in the term # 2) the coefficient part of the term # TODO: possibly refactor this producing a class ExchangeRelation with some pretty printing feature self._exchange_relations = dict() if 'store_exchange_relations' in kwargs and kwargs['store_exchange_relations']: self._store_exchange_relations = True else: self._store_exchange_relations = False # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) # TODO: understand if we need this #self._populate_coercion_lists_() def __copy__(self): other = type(self).__new__(type(self)) other._U = self._U other._path_dict = copy(self._path_dict) other._F_poly_dict = copy(self._F_poly_dict) other._ambient = self._ambient other._ambient_field = self._ambient_field other._y = copy(self._y) other._yhat = copy(self._yhat) other._is_principal = self._is_principal other._B0 = copy(self._B0) other._n = self._n # We probably need to put n=2 initializations here also # TODO: we may want to use __init__ to make the initialization somewhat easier (say to enable special cases) This might require a better written __init__ return other def __eq__(self, other): return type(self) == type(other) and self._B0 == other._B0 and self._yhat == other._yhat # enable standard coercions: everything that is in the base can be coerced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) def _repr_(self): return "Cluster Algebra of rank %s"%self.rk def _an_element_(self): return self.current_seed().cluster_variable(0) @property def rk(self): r""" The rank of ``self`` i.e. the number of cluster variables in any seed of ``self``. """ return self._n def current_seed(self): r""" The current seed of ``self``. """ return self._seed def set_current_seed(self, seed): r""" Set ``self._seed`` to ``seed`` if it makes sense. """ if self.contains_seed(seed): self._seed = seed else: raise ValueError("This is not a seed in this cluster algebra.") def contains_seed(self, seed): computed_sd = self.initial_seed computed_sd.mutate(seed._path, mutating_F=False) return computed_sd == seed def reset_current_seed(self): r""" Reset the current seed to the initial one """ self._seed = self.initial_seed @property def initial_seed(self): r""" Return the initial seed """ n = self.rk I = identity_matrix(n) return ClusterAlgebraSeed(self._B0, I, I, self) @property def initial_b_matrix(self): n = self.rk return copy(self._B0) def g_vectors_so_far(self): r""" Return the g-vectors of cluster variables encountered so far. """ return self._path_dict.keys() def F_polynomial(self, g_vector): g_vector= tuple(g_vector) try: return self._F_poly_dict[g_vector] except: # If the path is known, should this method perform that sequence of mutations to compute the desired F-polynomial? # Yes, perhaps with the a prompt first, something like: #comp = raw_input("This F-polynomial has not been computed yet. It can be found using %s mutations. Continue? (y or n):"%str(directions.__len__())) #if comp == 'y': # ...compute the F-polynomial... if g_vector in self._path_dict: raise ValueError("The F-polynomial with g-vector %s has not been computed yet. You probably explored the exchange tree with compute_F=False. You can compute this F-polynomial by mutating from the initial seed along the sequence %s."%(str(g_vector),str(self._path_dict[g_vector]))) else: raise ValueError("The F-polynomial with g-vector %s has not been computed yet."%str(g_vector)) @cached_method(key=lambda a,b: tuple(b) ) def cluster_variable(self, g_vector): g_vector = tuple(g_vector) if not g_vector in self.g_vectors_so_far(): # Should we let the self.F_polynomial below handle raising the exception? raise ValueError("This Cluster Variable has not been computed yet.") F_std = self.F_polynomial(g_vector).subs(self._yhat) g_mon = prod([self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field F_trop = self.ambient_field()(self.F_polynomial(g_vector).subs(self._y)).denominator() return self.retract(g_mon*F_std*F_trop) def find_cluster_variable(self, g_vector, depth=infinity): r""" Returns the shortest mutation path to obtain the cluster variable with g-vector ``g_vector`` from the initial seed. ``depth``: maximum distance from ``self.current_seed`` to reach. WARNING: if this method is interrupted then ``self._sd_iter`` is left in an unusable state. To use again this method it is then necessary to reset ``self._sd_iter`` via self.reset_exploring_iterator() """ g_vector = tuple(g_vector) mutation_counter = 0 while g_vector not in self.g_vectors_so_far() and self._explored_depth <= depth: try: seed = next(self._sd_iter) self._explored_depth = seed.depth() except: raise ValueError("Could not find a cluster variable with g-vector %s up to mutation depth %s after performing %s mutations."%(str(g_vector),str(depth),str(mutation_counter))) # If there was a way to have the seeds iterator continue after the depth_counter reaches depth, # the following code would allow the user to continue searching the exchange graph #cont = raw_input("Could not find a cluster variable with g-vector %s up to mutation depth %s."%(str(g_vector),str(depth))+" Continue searching? (y or n):") #if cont == 'y': # new_depth = 0 # while new_depth <= depth: # new_depth = raw_input("Please enter a new mutation search depth greater than %s:"%str(depth)) # seeds.send(new_depth) #else: # raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) mutation_counter += 1 return copy(self._path_dict[g_vector]) def ambient(self): return self._ambient def ambient_field(self): return self._ambient_field def lift_to_field(self, x): return self.ambient_field()(1)*x.value def lift(self, x): r""" Return x as an element of self._ambient """ return x.value def retract(self, x): return self(x) def gens(self): r""" Return the generators of :meth:`self.ambient` """ return map(self.retract, self.ambient().gens()) def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): r""" Return an iterator producing all seeds of ``self`` up to distance ``depth`` from ``self.initial_seed`` or ``self.current_seed``. If ``mutating_F`` is set to false it does not compute F_polynomials """ if from_current_seed: seed = self.current_seed() else: seed = self.initial_seed yield seed depth_counter = 0 n = self.rk cl = frozenset(seed.g_vectors()) clusters = {} clusters[cl] = [ seed, range(n) ] gets_bigger = True while gets_bigger and depth_counter < depth: gets_bigger = False keys = clusters.keys() for key in keys: sd, directions = clusters[key] while directions: i = directions.pop() new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) new_cl = frozenset(new_sd.g_vectors()) if new_cl in clusters: j = map(tuple,clusters[new_cl][0].g_vectors()).index(new_sd.g_vector(i)) try: clusters[new_cl][1].remove(j) except: pass else: gets_bigger = True # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed new_directions = [ j for j in xrange(n) if j > i or new_sd.b_matrix()[j,i] != 0 ] clusters[new_cl] = [ new_sd, new_directions ] # Use this if we want to have the user pass info to the # iterator #new_depth = yield new_sd #if new_depth > depth: # depth = new_depth yield new_sd depth_counter += 1 def reset_exploring_iterator(self, mutating_F=True): self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 @mutation_parse def mutate_initial(self, k): r""" Mutate ``self`` in direction `k` at the initial cluster. INPUT: - ``k`` -- integer in between 0 and ``self.rk`` """ n = self.rk if k not in xrange(n): raise ValueError('Cannot mutate in direction %s, please try a value between 0 and %s.'%(str(k),str(n-1))) #modify self._path_dict using Nakanishi-Zelevinsky (4.1) and self._F_poly_dict using CA-IV (6.21) new_path_dict = dict() new_F_dict = dict() new_path_dict[tuple(identity_matrix(n).column(k))] = [] new_F_dict[tuple(identity_matrix(n).column(k))] = self._U(1) poly_ring = PolynomialRing(ZZ,'u') h_subs_tuple = tuple([poly_ring.gen(0)**(-1) if j==k else poly_ring.gen(0)**max(-self._B0[k][j],0) for j in xrange(n)]) F_subs_tuple = tuple([self._U.gen(k)**(-1) if j==k else self._U.gen(j)*self._U.gen(k)**max(-self._B0[k][j],0)*(1+self._U.gen(k))**(self._B0[k][j]) for j in xrange(n)]) for g_vect in self._path_dict: #compute new path path = self._path_dict[g_vect] if g_vect == tuple(identity_matrix(n).column(k)): new_path = [k] elif path != []: if path[0] != k: new_path = [k] + path else: new_path = path[1:] else: new_path = [] #compute new g-vector new_g_vect = vector(g_vect) - 2*g_vect[k]*identity_matrix(n).column(k) for i in xrange(n): new_g_vect += max(sign(g_vect[k])*self._B0[i,k],0)*g_vect[k]*identity_matrix(n).column(i) new_path_dict[tuple(new_g_vect)] = new_path #compute new F-polynomial h = 0 trop = tropical_evaluation(self._F_poly_dict[g_vect](h_subs_tuple)) if trop != 1: h = trop.denominator().exponents()[0]-trop.numerator().exponents()[0] new_F_dict[tuple(new_g_vect)] = self._F_poly_dict[g_vect](F_subs_tuple)*self._U.gen(k)**h*(self._U.gen(k)+1)**g_vect[k] self._path_dict = new_path_dict self._F_poly_dict = new_F_dict self._B0.mutate(k) def explore_to_depth(self, depth): while self._explored_depth <= depth: try: seed = next(self._sd_iter) self._explored_depth = seed.depth() except: break def cluster_fan(self, depth=infinity): from sage.geometry.cone import Cone from sage.geometry.fan import Fan seeds = self.seeds(depth=depth, mutating_F=False) cones = map(lambda s: Cone(s.g_vectors()), seeds) return Fan(cones) # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): pass def upper_bound(self): pass def lower_bound(self): pass
class ClusterAlgebra(Parent): r""" INPUT: - ``data`` -- some data defining a cluster algebra. - ``scalars`` -- (default ZZ) the scalars on which the cluster algebra is defined. - ``cluster_variables_prefix`` -- string (default 'x'). - ``cluster_variables_names`` -- a list of strings. Superseedes ``cluster_variables_prefix``. - ``coefficients_prefix`` -- string (default 'y'). - ``coefficients_names`` -- a list of strings. Superseedes ``cluster_variables_prefix``. - ``principal_coefficients`` -- bool (default: False). Superseedes any coefficient defined by ``data``. """ Element = ClusterAlgebraElement def __init__(self, data, **kwargs): r""" See :class:`ClusterAlgebra` for full documentation. """ # TODO: right now we use ClusterQuiver to parse input data. It looks like a good idea but we should make sure it is. # TODO: in base replace LaurentPolynomialRing with the group algebra of a tropical semifield once it is implemented # Temporary variables Q = ClusterQuiver(data) n = Q.n() B0 = Q.b_matrix()[:n, :] I = identity_matrix(n) if 'principal_coefficients' in kwargs and kwargs[ 'principal_coefficients']: M0 = I else: M0 = Q.b_matrix()[n:, :] m = M0.nrows() # Ambient space for F-polynomials # NOTE: for speed purposes we need to have QQ here instead of the more natural ZZ. The reason is that _mutated_F is faster if we do not cast the result to polynomials but then we get "rational" coefficients self._U = PolynomialRing(QQ, ['u%s' % i for i in xrange(n)]) # Storage for computed data self._path_dict = dict([(v, []) for v in map(tuple, I.columns())]) self._F_poly_dict = dict([(v, self._U(1)) for v in self._path_dict]) # Determine the names of the initial cluster variables if 'cluster_variables_names' in kwargs: if len(kwargs['cluster_variables_names']) == n: variables = kwargs['cluster_variables_names'] cluster_variables_prefix = 'dummy' # this is just to avoid checking again if cluster_variables_prefix is defined. Make this better before going public else: raise ValueError( "cluster_variables_names should be a list of %d valid variable names" % n) else: try: cluster_variables_prefix = kwargs['cluster_variables_prefix'] except: cluster_variables_prefix = 'x' variables = [ cluster_variables_prefix + '%s' % i for i in xrange(n) ] # why not just put str(i) instead of '%s'%i? # Determine scalars try: scalars = kwargs['scalars'] except: scalars = ZZ # Determine coefficients and setup self._base if m > 0: if 'coefficients_names' in kwargs: if len(kwargs['coefficients_names']) == m: coefficients = kwargs['coefficients_names'] else: raise ValueError( "coefficients_names should be a list of %d valid variable names" % m) else: try: coefficients_prefix = kwargs['coefficients_prefix'] except: coefficients_prefix = 'y' if coefficients_prefix == cluster_variables_prefix: offset = n else: offset = 0 coefficients = [ coefficients_prefix + '%s' % i for i in xrange(offset, m + offset) ] # TODO: (***) base should eventually become the group algebra of a tropical semifield base = LaurentPolynomialRing(scalars, coefficients) else: base = scalars # TODO: next line should be removed when (***) is implemented coefficients = [] # setup Parent and ambient # TODO: (***) _ambient should eventually be replaced with LaurentPolynomialRing(base, variables) self._ambient = LaurentPolynomialRing(scalars, variables + coefficients) self._ambient_field = self._ambient.fraction_field() # TODO: understand why using Algebras() instead of Rings() makes A(1) complain of missing _lmul_ Parent.__init__(self, base=base, category=Rings(scalars).Commutative().Subobjects(), names=variables + coefficients) # Data to compute cluster variables using separation of additions # BUG WORKAROUND: if your sage installation does not have trac:`19538` merged uncomment the following line and comment the next self._y = dict([ (self._U.gen(j), prod([self._ambient.gen(n + i)**M0[i, j] for i in xrange(m)])) for j in xrange(n) ]) #self._y = dict([ (self._U.gen(j), prod([self._base.gen(i)**M0[i,j] for i in xrange(m)])) for j in xrange(n)]) self._yhat = dict([ (self._U.gen(j), prod([self._ambient.gen(i)**B0[i, j] for i in xrange(n)]) * self._y[self._U.gen(j)]) for j in xrange(n) ]) # Have we principal coefficients? self._is_principal = (M0 == I) # Store initial data self._B0 = copy(B0) self._n = n self.reset_current_seed() # Internal data for exploring the exchange graph self.reset_exploring_iterator() # Internal data to store exchange relations # This is a dictionary indexed by a frozen set of two g-vectors (the g-vectors of the exchanged variables) # Exchange relations are, for the moment, a frozen set of precisely two entries (one for each term in the exchange relation's RHS). # Each of them contains two things # 1) a list of pairs (g-vector, exponent) one for each cluster variable appearing in the term # 2) the coefficient part of the term # TODO: possibly refactor this producing a class ExchangeRelation with some pretty printing feature self._exchange_relations = dict() if 'store_exchange_relations' in kwargs and kwargs[ 'store_exchange_relations']: self._store_exchange_relations = True else: self._store_exchange_relations = False # Add methods that are defined only for special cases if n == 2: self.greedy_element = MethodType(greedy_element, self, self.__class__) self.greedy_coefficient = MethodType(greedy_coefficient, self, self.__class__) self.theta_basis_element = MethodType(theta_basis_element, self, self.__class__) # TODO: understand if we need this #self._populate_coercion_lists_() def __copy__(self): other = type(self).__new__(type(self)) other._U = self._U other._path_dict = copy(self._path_dict) other._F_poly_dict = copy(self._F_poly_dict) other._ambient = self._ambient other._ambient_field = self._ambient_field other._y = copy(self._y) other._yhat = copy(self._yhat) other._is_principal = self._is_principal other._B0 = copy(self._B0) other._n = self._n # We probably need to put n=2 initializations here also # TODO: we may want to use __init__ to make the initialization somewhat easier (say to enable special cases) This might require a better written __init__ return other def __eq__(self, other): return type(self) == type( other) and self._B0 == other._B0 and self._yhat == other._yhat # enable standard coercions: everything that is in the base can be coerced def _coerce_map_from_(self, other): return self.base().has_coerce_map_from(other) def _repr_(self): return "Cluster Algebra of rank %s" % self.rk def _an_element_(self): return self.current_seed().cluster_variable(0) @property def rk(self): r""" The rank of ``self`` i.e. the number of cluster variables in any seed of ``self``. """ return self._n def current_seed(self): r""" The current seed of ``self``. """ return self._seed def set_current_seed(self, seed): r""" Set ``self._seed`` to ``seed`` if it makes sense. """ if self.contains_seed(seed): self._seed = seed else: raise ValueError("This is not a seed in this cluster algebra.") def contains_seed(self, seed): computed_sd = self.initial_seed computed_sd.mutate(seed._path, mutating_F=False) return computed_sd == seed def reset_current_seed(self): r""" Reset the current seed to the initial one """ self._seed = self.initial_seed @property def initial_seed(self): r""" Return the initial seed """ n = self.rk I = identity_matrix(n) return ClusterAlgebraSeed(self._B0, I, I, self) @property def initial_b_matrix(self): n = self.rk return copy(self._B0) def g_vectors_so_far(self): r""" Return the g-vectors of cluster variables encountered so far. """ return self._path_dict.keys() def F_polynomial(self, g_vector): g_vector = tuple(g_vector) try: return self._F_poly_dict[g_vector] except: # If the path is known, should this method perform that sequence of mutations to compute the desired F-polynomial? # Yes, perhaps with the a prompt first, something like: #comp = raw_input("This F-polynomial has not been computed yet. It can be found using %s mutations. Continue? (y or n):"%str(directions.__len__())) #if comp == 'y': # ...compute the F-polynomial... if g_vector in self._path_dict: raise ValueError( "The F-polynomial with g-vector %s has not been computed yet. You probably explored the exchange tree with compute_F=False. You can compute this F-polynomial by mutating from the initial seed along the sequence %s." % (str(g_vector), str(self._path_dict[g_vector]))) else: raise ValueError( "The F-polynomial with g-vector %s has not been computed yet." % str(g_vector)) @cached_method(key=lambda a, b: tuple(b)) def cluster_variable(self, g_vector): g_vector = tuple(g_vector) if not g_vector in self.g_vectors_so_far(): # Should we let the self.F_polynomial below handle raising the exception? raise ValueError( "This Cluster Variable has not been computed yet.") F_std = self.F_polynomial(g_vector).subs(self._yhat) g_mon = prod( [self.ambient().gen(i)**g_vector[i] for i in xrange(self.rk)]) # LaurentPolynomial_mpair does not know how to compute denominators, we need to lift to its fraction field F_trop = self.ambient_field()(self.F_polynomial(g_vector).subs( self._y)).denominator() return self.retract(g_mon * F_std * F_trop) def find_cluster_variable(self, g_vector, depth=infinity): r""" Returns the shortest mutation path to obtain the cluster variable with g-vector ``g_vector`` from the initial seed. ``depth``: maximum distance from ``self.current_seed`` to reach. WARNING: if this method is interrupted then ``self._sd_iter`` is left in an unusable state. To use again this method it is then necessary to reset ``self._sd_iter`` via self.reset_exploring_iterator() """ g_vector = tuple(g_vector) mutation_counter = 0 while g_vector not in self.g_vectors_so_far( ) and self._explored_depth <= depth: try: seed = next(self._sd_iter) self._explored_depth = seed.depth() except: raise ValueError( "Could not find a cluster variable with g-vector %s up to mutation depth %s after performing %s mutations." % (str(g_vector), str(depth), str(mutation_counter))) # If there was a way to have the seeds iterator continue after the depth_counter reaches depth, # the following code would allow the user to continue searching the exchange graph #cont = raw_input("Could not find a cluster variable with g-vector %s up to mutation depth %s."%(str(g_vector),str(depth))+" Continue searching? (y or n):") #if cont == 'y': # new_depth = 0 # while new_depth <= depth: # new_depth = raw_input("Please enter a new mutation search depth greater than %s:"%str(depth)) # seeds.send(new_depth) #else: # raise ValueError("Could not find a cluster variable with g-vector %s after %s mutations."%(str(g_vector),str(mutation_counter))) mutation_counter += 1 return copy(self._path_dict[g_vector]) def ambient(self): return self._ambient def ambient_field(self): return self._ambient_field def lift_to_field(self, x): return self.ambient_field()(1) * x.value def lift(self, x): r""" Return x as an element of self._ambient """ return x.value def retract(self, x): return self(x) def gens(self): r""" Return the generators of :meth:`self.ambient` """ return map(self.retract, self.ambient().gens()) def seeds(self, depth=infinity, mutating_F=True, from_current_seed=False): r""" Return an iterator producing all seeds of ``self`` up to distance ``depth`` from ``self.initial_seed`` or ``self.current_seed``. If ``mutating_F`` is set to false it does not compute F_polynomials """ if from_current_seed: seed = self.current_seed() else: seed = self.initial_seed yield seed depth_counter = 0 n = self.rk cl = frozenset(seed.g_vectors()) clusters = {} clusters[cl] = [seed, range(n)] gets_bigger = True while gets_bigger and depth_counter < depth: gets_bigger = False keys = clusters.keys() for key in keys: sd, directions = clusters[key] while directions: i = directions.pop() new_sd = sd.mutate(i, inplace=False, mutating_F=mutating_F) new_cl = frozenset(new_sd.g_vectors()) if new_cl in clusters: j = map(tuple, clusters[new_cl][0].g_vectors()).index( new_sd.g_vector(i)) try: clusters[new_cl][1].remove(j) except: pass else: gets_bigger = True # doublecheck this way of producing directions for the new seed: it is taken almost verbatim fom ClusterSeed new_directions = [ j for j in xrange(n) if j > i or new_sd.b_matrix()[j, i] != 0 ] clusters[new_cl] = [new_sd, new_directions] # Use this if we want to have the user pass info to the # iterator #new_depth = yield new_sd #if new_depth > depth: # depth = new_depth yield new_sd depth_counter += 1 def reset_exploring_iterator(self, mutating_F=True): self._sd_iter = self.seeds(mutating_F=mutating_F) self._explored_depth = 0 @mutation_parse def mutate_initial(self, k): r""" Mutate ``self`` in direction `k` at the initial cluster. INPUT: - ``k`` -- integer in between 0 and ``self.rk`` """ n = self.rk if k not in xrange(n): raise ValueError( 'Cannot mutate in direction %s, please try a value between 0 and %s.' % (str(k), str(n - 1))) #modify self._path_dict using Nakanishi-Zelevinsky (4.1) and self._F_poly_dict using CA-IV (6.21) new_path_dict = dict() new_F_dict = dict() new_path_dict[tuple(identity_matrix(n).column(k))] = [] new_F_dict[tuple(identity_matrix(n).column(k))] = self._U(1) poly_ring = PolynomialRing(ZZ, 'u') h_subs_tuple = tuple([ poly_ring.gen(0)**(-1) if j == k else poly_ring.gen(0)**max( -self._B0[k][j], 0) for j in xrange(n) ]) F_subs_tuple = tuple([ self._U.gen(k)**(-1) if j == k else self._U.gen(j) * self._U.gen(k)**max(-self._B0[k][j], 0) * (1 + self._U.gen(k))**(self._B0[k][j]) for j in xrange(n) ]) for g_vect in self._path_dict: #compute new path path = self._path_dict[g_vect] if g_vect == tuple(identity_matrix(n).column(k)): new_path = [k] elif path != []: if path[0] != k: new_path = [k] + path else: new_path = path[1:] else: new_path = [] #compute new g-vector new_g_vect = vector( g_vect) - 2 * g_vect[k] * identity_matrix(n).column(k) for i in xrange(n): new_g_vect += max(sign(g_vect[k]) * self._B0[i, k], 0) * g_vect[k] * identity_matrix(n).column(i) new_path_dict[tuple(new_g_vect)] = new_path #compute new F-polynomial h = 0 trop = tropical_evaluation(self._F_poly_dict[g_vect](h_subs_tuple)) if trop != 1: h = trop.denominator().exponents()[0] - trop.numerator( ).exponents()[0] new_F_dict[tuple(new_g_vect)] = self._F_poly_dict[g_vect]( F_subs_tuple) * self._U.gen(k)**h * (self._U.gen(k) + 1)**g_vect[k] self._path_dict = new_path_dict self._F_poly_dict = new_F_dict self._B0.mutate(k) def explore_to_depth(self, depth): while self._explored_depth <= depth: try: seed = next(self._sd_iter) self._explored_depth = seed.depth() except: break def cluster_fan(self, depth=infinity): from sage.geometry.cone import Cone from sage.geometry.fan import Fan seeds = self.seeds(depth=depth, mutating_F=False) cones = map(lambda s: Cone(s.g_vectors()), seeds) return Fan(cones) # DESIDERATA. Some of these are probably unrealistic def upper_cluster_algebra(self): pass def upper_bound(self): pass def lower_bound(self): pass