Example #1
0
    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())
Example #2
0
    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)
Example #3
0
    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__(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)
Example #5
0
    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 __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())
Example #7
0
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)
Example #8
0
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))
Example #9
0
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))
Example #10
0
    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
Example #11
0
 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)
Example #12
0
    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)
Example #14
0
    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)
Example #15
0
    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)
Example #16
0
    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)
Example #17
0
    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())
Example #18
0
    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())
Example #19
0
    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())
Example #21
0
    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
Example #22
0
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
Example #23
0
    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)])
Example #25
0
    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
Example #26
0
    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
Example #27
0
    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__)
Example #28
0
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
Example #29
0
    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
Example #30
0
    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
Example #31
0
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)
Example #32
0
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
Example #33
0
    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)
Example #34
0
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)
Example #35
0
    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__)
Example #36
0
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
Example #37
0
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