class TestGradedModule(unittest.TestCase):
 
  def setUp(self):
    self.poly_ring = PolynomialRing(QQ,"x",3);
    self.x = self.poly_ring.gens()[0];
    self.y = self.poly_ring.gens()[1];
    self.z = self.poly_ring.gens()[2];

  def test_monomial_basis_zero(self):
    one = self.poly_ring.one()
    zero = self.poly_ring.zero()
    gm = GradedModule([[one,one,one,one]],[0,1,2,3],[1,2,3])
    self.assertEqual(gm.monomial_basis(0),[(one,zero,zero,zero)])

  def test_monomial_basis(self):
    x = self.x
    y = self.y
    one = self.poly_ring.one()
    zero = self.poly_ring.zero()
    gm = GradedModule([[one,one]],[0,1],[1,2,3])
    true_basis  = [(x**2,zero),(y,zero),(zero,x)]
    self.assertEqual(Set(gm.monomial_basis(2)),Set(true_basis))

  def test_homogeneous_parts_A(self):
    one = self.poly_ring.one()
    zero = self.poly_ring.zero()
    gm = GradedModule([[one,one]],[0,1],[1,2,3])
    parts = gm.get_homogeneous_parts([one,one])
    parts_true = { 0:[one,zero] , 1:[zero,one] }
    self.assertEqual(parts,parts_true)

  def test_homogeneous_parts_B(self):
    x = self.x
    y = self.y
    z = self.z
    one = self.poly_ring.one()
    zero = self.poly_ring.zero()
    gm = GradedModule([[one,one]],[0,1],[1,2,3])
    parts = gm.get_homogeneous_parts([x*y,x**3+z*y])
    self.assertEqual(parts,{3:[x*y,zero],4:[zero,x**3],6:[zero,z*y]})
  
  def test_homogeneous_part_basisA(self):
    x = self.x
    y = self.y
    z = self.z
    one = self.poly_ring.one()
    gm = GradedModule([[z,one,x**2 + y]],[0,1,2],[1,2,3])
    basis = gm.homogeneous_part_basis(6);
    self.assertEqual(len(basis),10)
 
  def test_homogeneous_part_basisB(self):
    #From bug found with ncd
    x = self.x
    y = self.y
    z = self.z
    zero = self.poly_ring.zero()
    gm = GradedModule([[zero, x*z, x*y], [zero, -x*z, zero], [y*z, zero, zero]],[1, 1, 1],[1, 1, 1])
    basis = gm.homogeneous_part_basis(3);
    self.assertEqual(len(basis),3)
class TestMonomialsOfOrder(unittest.TestCase):
 
  def setUp(self):
    self.poly_ring = PolynomialRing(QQ,"x",3);
    self.x = self.poly_ring.gens()[0];
    self.y = self.poly_ring.gens()[1];
    self.z = self.poly_ring.gens()[2];
  
  def test_zero(self):
    mons = [mon for mon in monomials_of_order(0,self.poly_ring,[1,1,1])]
    self.assertEqual(mons,[self.poly_ring.one()])

  def test_homogeneous_3(self):
    x = self.x;
    y = self.y;
    z = self.z;
    true_mons = Set([x**3,y**3,z**3,x**2*y,x**2*z,y**2*x,y**2*z,z**2*x,z**2*y,x*y*z])
    mons = [mon for mon in monomials_of_order(3,self.poly_ring,[1,1,1])]
    self.assertEqual(true_mons,Set(mons))

  def test_non_homogeneous_4(self):
    x = self.x;
    y = self.y;
    z = self.z;
    true_mons = Set([x**4,x**2*y,x*z,y**2])
    mons = [mon for mon in monomials_of_order(4,self.poly_ring,[1,2,3])]
    self.assertEqual(true_mons,Set(mons))
def rand_w_hom_divisor(n,degs=None,mon_num=None,var="z"):
  if degs==None:
    degs = [randrange(2,6) for _ in range(n)]
  deg = sum(degs)
  if mon_num==None:
    mon_num = randrange(2,8)
  poly_ring = PolynomialRing(QQ,n,var)
  div = poly_ring.zero()
  min_w = min(degs)
  for i in range(mon_num):
    expo = [0]*n
    cur_deg = 0
    while cur_deg!=deg:
      if cur_deg>deg:
        expo = [0]*n
        cur_deg = 0
      if deg-cur_deg<min_w:
        expo = [0]*n
        cur_deg = 0
      next_g = randrange(0,n)
      expo[next_g] += 1
      cur_deg += degs[next_g]
    coeff = randrange(-n,n)/n
    mon = poly_ring.one()
    for i,e in enumerate(expo):
      mon *= poly_ring.gens()[i]**e
    div += coeff*mon
  return div
Example #4
0
def rand_w_hom_divisor(n, degs=None, mon_num=None, var="z"):
    if degs == None:
        degs = [randrange(2, 6) for _ in range(n)]
    deg = sum(degs)
    if mon_num == None:
        mon_num = randrange(2, 8)
    poly_ring = PolynomialRing(QQ, n, var)
    div = poly_ring.zero()
    min_w = min(degs)
    for i in range(mon_num):
        expo = [0] * n
        cur_deg = 0
        while cur_deg != deg:
            if cur_deg > deg:
                expo = [0] * n
                cur_deg = 0
            if deg - cur_deg < min_w:
                expo = [0] * n
                cur_deg = 0
            next_g = randrange(0, n)
            expo[next_g] += 1
            cur_deg += degs[next_g]
        coeff = randrange(-n, n) / n
        mon = poly_ring.one()
        for i, e in enumerate(expo):
            mon *= poly_ring.gens()[i]**e
        div += coeff * mon
    return div
def braid_divisor(n,var="z"):
  poly_ring = PolynomialRing(QQ,n,var)
  div = poly_ring.one()
  gens = poly_ring.gens()
  for i in range(n):
    for j in range(i+1,n):
      div *= (gens[i]-gens[j])
  return div
Example #6
0
def braid_divisor(n, var="z"):
    poly_ring = PolynomialRing(QQ, n, var)
    div = poly_ring.one()
    gens = poly_ring.gens()
    for i in range(n):
        for j in range(i + 1, n):
            div *= (gens[i] - gens[j])
    return div
 def test_p_module_n_crossing(self):
   #Make sure this doesnt throw an error - fix bug
   for i in range(4,5):
     p_ring = PolynomialRing(QQ,i,"z")
     crossing = p_ring.one()
     for g in p_ring.gens():
       crossing *= g
     logdf = LogarithmicDifferentialForms(crossing)
     logdf.p_module(i-1)
Example #8
0
def _step_upper_bound_low_mem(r, m, q, generating_function):
    r"""
    Low memory implementation of :func:`_step_upper_bound_internal`.

    Significantly slower, but the memory footprint does not significantly
    increase even if the series coefficients need to be computed to very high
    degree terms.
    """
    L = LieAlgebra(ZZ, ['X_%d' % k for k in range(r)]).Lyndon()
    dim_fm = L.graded_dimension(m)

    PR = PolynomialRing(ZZ, 't')
    t = PR.gen()
    a = (1 - dim_fm * (1 - t**q)) * t**m
    b = PR.one()
    for k in range(1, m):
        b *= (1 - t**k)**L.graded_dimension(k)

    # extract initial coefficients from a symbolic series expansion
    bd = b.degree()
    id = max(a.degree() + 1, bd)
    offset = id - bd
    quot = SR(a / b)
    sym_t = SR(t)
    qs = quot.series(sym_t, id)

    # check if partial sum is positive already within series expansion
    # store the last offset...id terms to start the linear recurrence
    coeffs = deque()
    cumul = ZZ.zero()
    for s in range(id):
        c = ZZ(qs.coefficient(sym_t, s))
        cumul += c
        if s >= offset:
            coeffs.append(c)
        if cumul > 0:
            if generating_function:
                return s, quot
            return s

    # the rest of the coefficients are defined by a recurrence relation
    multipliers = [-b.monomial_coefficient(t**(bd - k)) for k in range(bd)]
    while cumul <= 0:
        c_next = sum(c * m for c, m in zip(coeffs, multipliers))
        cumul += c_next
        s += 1
        coeffs.append(c_next)
        coeffs.popleft()

    if generating_function:
        return s, quot
    return s
    def product_on_basis(self, left, right):
        r"""
        Return ``left`` multiplied by ``right`` in ``self``.

        EXAMPLES::

            sage: R = algebras.RationalCherednik(['A',2], 1, 1, QQ)
            sage: a2 = R.algebra_generators()['a2']
            sage: ac1 = R.algebra_generators()['ac1']
            sage: a2 * ac1  # indirect doctest
            a2*ac1
            sage: ac1 * a2
            -I + a2*ac1 - s1 - s2 + 1/2*s1*s2*s1
            sage: x = R.an_element()
            sage: [y * x for y in R.some_elements()]
            [0,
             3*ac1 + 2*s1 + a1,
             9*ac1^2 + 10*I + 6*a1*ac1 + 6*s1 + 3/2*s2 + 3/2*s1*s2*s1 + a1^2,
             3*a1*ac1 + 2*a1*s1 + a1^2,
             3*a2*ac1 + 2*a2*s1 + a1*a2,
             3*s1*ac1 + 2*I - a1*s1,
             3*s2*ac1 + 2*s2*s1 + a1*s2 + a2*s2,
             3*ac1^2 - 2*s1*ac1 + 2*I + a1*ac1 + 2*s1 + 1/2*s2 + 1/2*s1*s2*s1,
             3*ac1*ac2 + 2*s1*ac1 + 2*s1*ac2 - I + a1*ac2 - s1 - s2 + 1/2*s1*s2*s1]
            sage: [x * y for y in R.some_elements()]
            [0,
             3*ac1 + 2*s1 + a1,
             9*ac1^2 + 10*I + 6*a1*ac1 + 6*s1 + 3/2*s2 + 3/2*s1*s2*s1 + a1^2,
             6*I + 3*a1*ac1 + 6*s1 + 3/2*s2 + 3/2*s1*s2*s1 - 2*a1*s1 + a1^2,
             -3*I + 3*a2*ac1 - 3*s1 - 3*s2 + 3/2*s1*s2*s1 + 2*a1*s1 + 2*a2*s1 + a1*a2,
             -3*s1*ac1 + 2*I + a1*s1,
             3*s2*ac1 + 3*s2*ac2 + 2*s1*s2 + a1*s2,
             3*ac1^2 + 2*s1*ac1 + a1*ac1,
             3*ac1*ac2 + 2*s1*ac2 + a1*ac2]
        """
        # Make copies of the internal dictionaries
        dl = dict(left[2]._monomial)
        dr = dict(right[0]._monomial)

        # If there is nothing to commute
        if not dl and not dr:
            return self.monomial((left[0], left[1] * right[1], right[2]))

        R = self.base_ring()
        I = self._cartan_type.index_set()
        P = PolynomialRing(R, 'x', len(I))
        G = P.gens()
        gens_dict = {a: G[i] for i, a in enumerate(I)}
        Q = RootSystem(self._cartan_type).root_lattice()
        alpha = Q.simple_roots()
        alphacheck = Q.simple_coroots()

        def commute_w_hd(w, al):  # al is given as a dictionary
            ret = P.one()
            for k in al:
                x = sum(c * gens_dict[i] for i, c in alpha[k].weyl_action(w))
                ret *= x**al[k]
            ret = ret.dict()
            for k in ret:
                yield (self._hd({I[i]: e
                                 for i, e in enumerate(k) if e != 0}), ret[k])

        # Do Lac Ra if they are both non-trivial
        if dl and dr:
            il = dl.keys()[0]
            ir = dr.keys()[0]

            # Compute the commutator
            terms = self._product_coroot_root(il, ir)

            # remove the generator from the elements
            dl[il] -= 1
            if dl[il] == 0:
                del dl[il]
            dr[ir] -= 1
            if dr[ir] == 0:
                del dr[ir]

            # We now commute right roots past the left reflections: s Ra = Ra' s
            cur = self._from_dict({(hd, s * right[1], right[2]): c * cc
                                   for s, c in terms
                                   for hd, cc in commute_w_hd(s, dr)})
            cur = self.monomial((left[0], left[1], self._h(dl))) * cur

            # Add back in the commuted h and hd elements
            rem = self.monomial((left[0], left[1], self._h(dl)))
            rem = rem * self.monomial(
                (self._hd({ir: 1}), self._weyl.one(), self._h({il: 1})))
            rem = rem * self.monomial((self._hd(dr), right[1], right[2]))

            return cur + rem

        if dl:
            # We have La Ls Lac Rs Rac,
            #   so we must commute Lac Rs = Rs Lac'
            #   and obtain La (Ls Rs) (Lac' Rac)
            ret = P.one()
            for k in dl:
                x = sum(c * gens_dict[i] for i, c in alphacheck[k].weyl_action(
                    right[1].reduced_word(), inverse=True))
                ret *= x**dl[k]
            ret = ret.dict()
            w = left[1] * right[1]
            return self._from_dict({
                (left[0], w,
                 self._h({I[i]: e
                          for i, e in enumerate(k) if e != 0}) * right[2]):
                ret[k]
                for k in ret
            })

        # Otherwise dr is non-trivial and we have La Ls Ra Rs Rac,
        #   so we must commute Ls Ra = Ra' Ls
        w = left[1] * right[1]
        return self._from_dict({(left[0] * hd, w, right[2]): c
                                for hd, c in commute_w_hd(left[1], dr)})
Example #10
0
class QSystem(CombinatorialFreeModule):
    r"""
    A Q-system.

    Let `\mathfrak{g}` be a tamely-laced symmetrizable Kac-Moody algebra
    with index set `I` and Cartan matrix `(C_{ab})_{a,b \in I}` over a
    field `k`. Follow the presentation given in [HKOTY1999]_, an
    unrestricted Q-system is a `k`-algebra in infinitely many variables
    `Q^{(a)}_m`, where `a \in I` and `m \in \ZZ_{>0}`, that satisfies
    the relations

    .. MATH::

        \left(Q^{(a)}_m\right)^2 = Q^{(a)}_{m+1} Q^{(a)}_{m-1} +
        \prod_{b \sim a} \prod_{k=0}^{-C_{ab} - 1}
        Q^{(b)}_{\left\lfloor \frac{m C_{ba} - k}{C_{ab}} \right\rfloor},

    with `Q^{(a)}_0 := 1`. Q-systems can be considered as T-systems where
    we forget the spectral parameter `u` and for `\mathfrak{g}` of finite
    type, have a solution given by the characters of Kirillov-Reshetikhin
    modules (again without the spectral parameter) for an affine Kac-Moody
    algebra `\widehat{\mathfrak{g}}` with `\mathfrak{g}` as its classical
    subalgebra. See [KNS2011]_ for more information.

    Q-systems have a natural bases given by polynomials of the
    fundamental representations `Q^{(a)}_1`, for `a \in I`. As such, we
    consider the Q-system as generated by `\{ Q^{(a)}_1 \}_{a \in I}`.

    There is also a level `\ell` restricted Q-system (with unit boundary
    condition) given by setting `Q_{d_a \ell}^{(a)} = 1`, where `d_a`
    are the entries of the symmetrizing matrix for the dual type of
    `\mathfrak{g}`.

    Similarly, for twisted affine types (we omit type `A_{2n}^{(2)}`),
    we can define the *twisted Q-system* by using the relation:

    .. MATH::

        (Q^{(a)}_{m})^2 = Q^{(a)}_{m+1} Q^{(a)}_{m-1}
         + \prod_{b \neq a} (Q^{(b)}_{m})^{-C_{ba}}.

    See [Wil2013]_ for more information.

    EXAMPLES:

    We begin by constructing a Q-system and doing some basic computations
    in type `A_4`::

        sage: Q = QSystem(QQ, ['A', 4])
        sage: Q.Q(3,1)
        Q^(3)[1]
        sage: Q.Q(1,2)
        Q^(1)[1]^2 - Q^(2)[1]
        sage: Q.Q(3,3)
        -Q^(1)[1]*Q^(3)[1] + Q^(1)[1]*Q^(4)[1]^2 + Q^(2)[1]^2
         - 2*Q^(2)[1]*Q^(3)[1]*Q^(4)[1] + Q^(3)[1]^3
        sage: x = Q.Q(1,1) + Q.Q(2,1); x
        Q^(1)[1] + Q^(2)[1]
        sage: x * x
        Q^(1)[1]^2 + 2*Q^(1)[1]*Q^(2)[1] + Q^(2)[1]^2

    Next we do some basic computations in type `C_4`::

        sage: Q = QSystem(QQ, ['C', 4])
        sage: Q.Q(4,1)
        Q^(4)[1]
        sage: Q.Q(1,2)
        Q^(1)[1]^2 - Q^(2)[1]
        sage: Q.Q(2,3)
        Q^(1)[1]^2*Q^(4)[1] - 2*Q^(1)[1]*Q^(2)[1]*Q^(3)[1]
         + Q^(2)[1]^3 - Q^(2)[1]*Q^(4)[1] + Q^(3)[1]^2
        sage: Q.Q(3,3)
        Q^(1)[1]*Q^(4)[1]^2 - 2*Q^(2)[1]*Q^(3)[1]*Q^(4)[1] + Q^(3)[1]^3

    We compare that with the twisted Q-system of type `A_7^{(2)}`::

        sage: Q = QSystem(QQ, ['A',7,2], twisted=True)
        sage: Q.Q(4,1)
        Q^(4)[1]
        sage: Q.Q(1,2)
        Q^(1)[1]^2 - Q^(2)[1]
        sage: Q.Q(2,3)
        Q^(1)[1]^2*Q^(4)[1] - 2*Q^(1)[1]*Q^(2)[1]*Q^(3)[1]
         + Q^(2)[1]^3 - Q^(2)[1]*Q^(4)[1] + Q^(3)[1]^2
        sage: Q.Q(3,3)
        -Q^(1)[1]*Q^(3)[1]^2 + Q^(1)[1]*Q^(4)[1]^2 + Q^(2)[1]^2*Q^(3)[1]
         - 2*Q^(2)[1]*Q^(3)[1]*Q^(4)[1] + Q^(3)[1]^3

    REFERENCES:

    - [HKOTY1999]_
    - [KNS2011]_
    """
    @staticmethod
    def __classcall__(cls, base_ring, cartan_type, level=None, twisted=False):
        """
        Normalize arguments to ensure a unique representation.

        EXAMPLES::

            sage: Q1 = QSystem(QQ, ['A',4])
            sage: Q2 = QSystem(QQ, 'A4')
            sage: Q1 is Q2
            True

        Twisted Q-systems are different from untwisted Q-systems::

            sage: Q1 = QSystem(QQ, ['E',6,2], twisted=True)
            sage: Q2 = QSystem(QQ, ['E',6,2])
            sage: Q1 is Q2
            False
        """
        cartan_type = CartanType(cartan_type)
        if not is_tamely_laced(cartan_type):
            raise ValueError("the Cartan type is not tamely-laced")
        if twisted and not cartan_type.is_affine(
        ) and not cartan_type.is_untwisted_affine():
            raise ValueError("the Cartan type must be of twisted type")
        return super(QSystem, cls).__classcall__(cls, base_ring, cartan_type,
                                                 level, twisted)

    def __init__(self, base_ring, cartan_type, level, twisted):
        """
        Initialize ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',2])
            sage: TestSuite(Q).run()

            sage: Q = QSystem(QQ, ['E',6,2], twisted=True)
            sage: TestSuite(Q).run()
        """
        self._cartan_type = cartan_type
        self._level = level
        self._twisted = twisted
        indices = tuple(itertools.product(cartan_type.index_set(), [1]))
        basis = IndexedFreeAbelianMonoid(indices, prefix='Q', bracket=False)
        # This is used to do the reductions
        if self._twisted:
            self._cm = cartan_type.classical().cartan_matrix()
        else:
            self._cm = cartan_type.cartan_matrix()
        self._Irev = {ind: pos for pos, ind in enumerate(self._cm.index_set())}
        self._poly = PolynomialRing(
            ZZ, ['q' + str(i) for i in self._cm.index_set()])

        category = Algebras(base_ring).Commutative().WithBasis()
        CombinatorialFreeModule.__init__(self,
                                         base_ring,
                                         basis,
                                         prefix='Q',
                                         category=category)

    def _repr_(self):
        r"""
        Return a string representation of ``self``.

        EXAMPLES::

            sage: QSystem(QQ, ['A',4])
            Q-system of type ['A', 4] over Rational Field

            sage: QSystem(QQ, ['A',7,2], twisted=True)
            Twisted Q-system of type ['B', 4, 1]^* over Rational Field
        """
        if self._level is not None:
            res = "Restricted level {} ".format(self._level)
        else:
            res = ''
        if self._twisted:
            res += "Twisted "
        return "{}Q-system of type {} over {}".format(res, self._cartan_type,
                                                      self.base_ring())

    def _repr_term(self, t):
        """
        Return a string representation of the basis element indexed by ``t``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: I = Q._indices
            sage: Q._repr_term( I.gen((1,1)) * I.gen((4,1)) )
            'Q^(1)[1]*Q^(4)[1]'
        """
        if len(t) == 0:
            return '1'

        def repr_gen(x):
            ret = 'Q^({})[{}]'.format(*(x[0]))
            if x[1] > 1:
                ret += '^{}'.format(x[1])
            return ret

        return '*'.join(repr_gen(x) for x in t._sorted_items())

    def _latex_term(self, t):
        r"""
        Return a `\LaTeX` representation of the basis element indexed
        by ``t``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: I = Q._indices
            sage: Q._latex_term( I.gen((3,1)) * I.gen((4,1)) )
            'Q^{(3)}_{1} Q^{(4)}_{1}'
        """
        if len(t) == 0:
            return '1'

        def repr_gen(x):
            ret = 'Q^{{({})}}_{{{}}}'.format(*(x[0]))
            if x[1] > 1:
                ret = '\\bigl(' + ret + '\\bigr)^{{{}}}'.format(x[1])
            return ret

        return ' '.join(repr_gen(x) for x in t._sorted_items())

    def _ascii_art_term(self, t):
        """
        Return an ascii art representation of the term indexed by ``t``.

        TESTS::

            sage: Q = QSystem(QQ, ['A',4])
            sage: ascii_art(Q.an_element())
                               2       2       3
                   (1)   ( (1))  ( (2))  ( (3))       (2)
            1 + 2*Q1   + (Q1  ) *(Q1  ) *(Q1  )  + 3*Q1
        """
        from sage.typeset.ascii_art import AsciiArt
        if t == self.one_basis():
            return AsciiArt(["1"])
        ret = AsciiArt("")
        first = True
        for k, exp in t._sorted_items():
            if not first:
                ret += AsciiArt(['*'], baseline=0)
            else:
                first = False
            a, m = k
            var = AsciiArt([" ({})".format(a), "Q{}".format(m)], baseline=0)
            #print var
            #print " "*(len(str(m))+1) + "({})".format(a) + '\n' + "Q{}".format(m)
            if exp > 1:
                var = (AsciiArt(['(', '('], baseline=0) + var +
                       AsciiArt([')', ')'], baseline=0))
                var = AsciiArt([" " * len(var) + str(exp)], baseline=-1) * var
            ret += var
        return ret

    def _unicode_art_term(self, t):
        r"""
        Return a unicode art representation of the term indexed by ``t``.

        TESTS::

            sage: Q = QSystem(QQ, ['A',4])
            sage: unicode_art(Q.an_element())
            1 + 2*Q₁⁽¹⁾ + (Q₁⁽¹⁾)²(Q₁⁽²⁾)²(Q₁⁽³⁾)³ + 3*Q₁⁽²⁾
        """
        from sage.typeset.unicode_art import UnicodeArt
        if t == self.one_basis():
            return UnicodeArt(["1"])

        subs = {
            '0': u'₀',
            '1': u'₁',
            '2': u'₂',
            '3': u'₃',
            '4': u'₄',
            '5': u'₅',
            '6': u'₆',
            '7': u'₇',
            '8': u'₈',
            '9': u'₉'
        }
        sups = {
            '0': u'⁰',
            '1': u'¹',
            '2': u'²',
            '3': u'³',
            '4': u'⁴',
            '5': u'⁵',
            '6': u'⁶',
            '7': u'⁷',
            '8': u'⁸',
            '9': u'⁹'
        }

        def to_super(x):
            return u''.join(sups[i] for i in str(x))

        def to_sub(x):
            return u''.join(subs[i] for i in str(x))

        ret = UnicodeArt("")
        for k, exp in t._sorted_items():
            a, m = k
            var = UnicodeArt([u"Q" + to_sub(m) + u'⁽' + to_super(a) + u'⁾'],
                             baseline=0)
            if exp > 1:
                var = (UnicodeArt([u'('], baseline=0) + var +
                       UnicodeArt([u')' + to_super(exp)], baseline=0))
            ret += var
        return ret

    def cartan_type(self):
        """
        Return the Cartan type of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.cartan_type()
            ['A', 4]

            sage: Q = QSystem(QQ, ['D',4,3], twisted=True)
            sage: Q.cartan_type()
            ['G', 2, 1]^* relabelled by {0: 0, 1: 2, 2: 1}
        """
        return self._cartan_type

    def index_set(self):
        """
        Return the index set of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.index_set()
            (1, 2, 3, 4)

            sage: Q = QSystem(QQ, ['D',4,3], twisted=True)
            sage: Q.index_set()
            (1, 2)
        """
        return self._cm.index_set()

    def level(self):
        """
        Return the restriction level of ``self`` or ``None`` if
        the system is unrestricted.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.level()

            sage: Q = QSystem(QQ, ['A',4], 5)
            sage: Q.level()
            5
        """
        return self._level

    @cached_method
    def one_basis(self):
        """
        Return the basis element indexing `1`.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.one_basis()
            1
            sage: Q.one_basis().parent() is Q._indices
            True
        """
        return self._indices.one()

    @cached_method
    def algebra_generators(self):
        """
        Return the algebra generators of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.algebra_generators()
            Finite family {1: Q^(1)[1], 2: Q^(2)[1], 3: Q^(3)[1], 4: Q^(4)[1]}

            sage: Q = QSystem(QQ, ['D',4,3], twisted=True)
            sage: Q.algebra_generators()
            Finite family {1: Q^(1)[1], 2: Q^(2)[1]}
        """
        I = self._cm.index_set()
        d = {a: self.Q(a, 1) for a in I}
        return Family(I, d.__getitem__)

    def gens(self):
        """
        Return the generators of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.gens()
            (Q^(1)[1], Q^(2)[1], Q^(3)[1], Q^(4)[1])
        """
        return tuple(self.algebra_generators())

    def dimension(self):
        """
        Return the dimension of ``self``, which is `\infty`.

        EXAMPLES::

            sage: F = QSystem(QQ, ['A',4])
            sage: F.dimension()
            +Infinity
        """
        return infinity

    def Q(self, a, m):
        r"""
        Return the generator `Q^{(a)}_m` of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A', 8])
            sage: Q.Q(2, 1)
            Q^(2)[1]
            sage: Q.Q(6, 2)
            -Q^(5)[1]*Q^(7)[1] + Q^(6)[1]^2
            sage: Q.Q(7, 3)
            -Q^(5)[1]*Q^(7)[1] + Q^(5)[1]*Q^(8)[1]^2 + Q^(6)[1]^2
             - 2*Q^(6)[1]*Q^(7)[1]*Q^(8)[1] + Q^(7)[1]^3
            sage: Q.Q(1, 0)
            1

        Twisted Q-system::

            sage: Q = QSystem(QQ, ['D',4,3], twisted=True)
            sage: Q.Q(1,2)
            Q^(1)[1]^2 - Q^(2)[1]
            sage: Q.Q(2,2)
            -Q^(1)[1]^3 + Q^(2)[1]^2
            sage: Q.Q(2,3)
            3*Q^(1)[1]^4 - 2*Q^(1)[1]^3*Q^(2)[1] - 3*Q^(1)[1]^2*Q^(2)[1]
             + Q^(2)[1]^2 + Q^(2)[1]^3
            sage: Q.Q(1,4)
            -2*Q^(1)[1]^2 + 2*Q^(1)[1]^3 + Q^(1)[1]^4
             - 3*Q^(1)[1]^2*Q^(2)[1] + Q^(2)[1] + Q^(2)[1]^2
        """
        if a not in self._cartan_type.index_set():
            raise ValueError("a is not in the index set")
        if m == 0:
            return self.one()
        if self._level:
            t = self._cartan_type.dual().cartan_matrix().symmetrizer()
            if m == t[a] * self._level:
                return self.one()
        if m == 1:
            return self.monomial(self._indices.gen((a, 1)))
        #if self._cartan_type.type() == 'A' and self._level is None:
        #    return self._jacobi_trudy(a, m)
        I = self._cm.index_set()
        p = self._Q_poly(a, m)
        return p.subs(
            {g: self.Q(I[i], 1)
             for i, g in enumerate(self._poly.gens())})

    @cached_method
    def _Q_poly(self, a, m):
        r"""
        Return the element `Q^{(a)}_m` as a polynomial.

        We start with the relation

        .. MATH::

            (Q^{(a)}_{m-1})^2 = Q^{(a)}_m Q^{(a)}_{m-2} + \mathcal{Q}_{a,m-1},

        which implies

        .. MATH::

            Q^{(a)}_m = \frac{Q^{(a)}_{m-1}^2 - \mathcal{Q}_{a,m-1}}{
            Q^{(a)}_{m-2}}.

        This becomes our relation used for reducing the Q-system to the
        fundamental representations.

        For twisted Q-systems, we use

        .. MATH::

            (Q^{(a)}_{m-1})^2 = Q^{(a)}_m Q^{(a)}_{m-2}
             + \prod_{b \neq a} (Q^{(b)}_{m-1})^{-A_{ba}}.

        .. NOTE::

            This helper method is defined in order to use the
            division implemented in polynomial rings.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',8])
            sage: Q._Q_poly(1, 2)
            q1^2 - q2
            sage: Q._Q_poly(3, 2)
            q3^2 - q2*q4
            sage: Q._Q_poly(6, 3)
            q6^3 - 2*q5*q6*q7 + q4*q7^2 + q5^2*q8 - q4*q6*q8

        Twisted types::

            sage: Q = QSystem(QQ, ['E',6,2], twisted=True)
            sage: Q._Q_poly(1,2)
            q1^2 - q2
            sage: Q._Q_poly(2,2)
            q2^2 - q1*q3
            sage: Q._Q_poly(3,2)
            -q2^2*q4 + q3^2
            sage: Q._Q_poly(4,2)
            q4^2 - q3
            sage: Q._Q_poly(3,3)
            2*q1*q2^2*q4^2 - q1^2*q3*q4^2 + q2^4 - 2*q1*q2^2*q3
             + q1^2*q3^2 - 2*q2^2*q3*q4 + q3^3

            sage: Q = QSystem(QQ, ['D',4,3], twisted=True)
            sage: Q._Q_poly(1,2)
            q1^2 - q2
            sage: Q._Q_poly(2,2)
            -q1^3 + q2^2
            sage: Q._Q_poly(1,3)
            q1^3 + q1^2 - 2*q1*q2
            sage: Q._Q_poly(2,3)
            3*q1^4 - 2*q1^3*q2 - 3*q1^2*q2 + q2^3 + q2^2
        """
        if m == 0 or m == self._level:
            return self._poly.one()
        if m == 1:
            return self._poly.gen(self._Irev[a])

        cm = self._cm
        m -= 1  # So we don't have to do it everywhere

        cur = self._Q_poly(a, m)**2
        if self._twisted:
            ret = prod(
                self._Q_poly(b, m)**-cm[self._Irev[b], self._Irev[a]]
                for b in self._cm.dynkin_diagram().neighbors(a))
        else:
            ret = self._poly.one()
            i = self._Irev[a]
            for b in self._cm.dynkin_diagram().neighbors(a):
                j = self._Irev[b]
                for k in range(-cm[i, j]):
                    ret *= self._Q_poly(b, (m * cm[j, i] - k) // cm[i, j])
        cur -= ret
        if m > 1:
            cur //= self._Q_poly(a, m - 1)
        return cur

    class Element(CombinatorialFreeModule.Element):
        """
        An element of a Q-system.
        """
        def _mul_(self, x):
            """
            Return the product of ``self`` and ``x``.

            EXAMPLES::

                sage: Q = QSystem(QQ, ['A',8])
                sage: x = Q.Q(1, 2)
                sage: y = Q.Q(3, 2)
                sage: x * y
                -Q^(1)[1]^2*Q^(2)[1]*Q^(4)[1] + Q^(1)[1]^2*Q^(3)[1]^2
                 + Q^(2)[1]^2*Q^(4)[1] - Q^(2)[1]*Q^(3)[1]^2
            """
            return self.parent().sum_of_terms(
                (tl * tr, cl * cr) for tl, cl in self for tr, cr in x)
class QuantumCliffordAlgebra(CombinatorialFreeModule):
    r"""
    The quantum Clifford algebra.

    The *quantum Clifford algebra*, or `q`-Clifford algebra,
    of rank `n` and twist `k` is the unital associative algebra
    `\mathrm{Cl}_{q}(n, k)` over a field `F` with generators
    `\psi_a, \psi_a^*, \omega_a` for `a = 1, \dotsc, n` that
    satisfy the following relations:

    .. MATH::

        \begin{aligned}
        \omega_a \omega_b & = \omega_b \omega_a,
        & \omega_a^{4k} & = (1 + q^{-2k}) \omega_a^{2k} - q^{-2k},
        \\ \omega_a \psi_b & = q^{\delta_{ab}} \psi_b \omega_a,
        & \omega_a \psi^*_b & = \psi^*_b \omega_a,
        \\ \psi_a \psi_b & + \psi_b \psi_a = 0,
        & \psi^*_a \psi^*_b & + \psi^*_b \psi^*_a = 0,
        \\ \psi_a \psi^*_a & = \frac{q^k \omega_a^{3k} - q^{-k} \omega_a^k}{q^k - q^{-k}},
        & \psi^*_a \psi_a & = \frac{q^{2k} (\omega_a - \omega_a^{3k})}{q^k - q^{-k}},
        \\ \psi_a \psi^*_b & + \psi_b^* \psi_a = 0
        & & \text{if } a \neq b,
        \end{aligned}

    where `q \in F` such that `q^{2k} \neq 1`.

    When `k = 2`, we recover the original definition given by Hayashi in
    [Hayashi1990]_. The `k = 1` version was used in [Kwon2014]_.

    INPUT:

    - ``n`` -- positive integer; the rank
    - ``k`` -- positive integer (default: 1); the twist
    - ``q`` -- (optional) the parameter `q`
    - ``F`` -- (default: `\QQ(q)`) the base field that contains ``q``

    EXAMPLES:

    We construct the rank 3 and twist 1 `q`-Clifford algebra::

        sage: Cl = algebras.QuantumClifford(3)
        sage: Cl
        Quantum Clifford algebra of rank 3 and twist 1 with q=q over
         Fraction Field of Univariate Polynomial Ring in q over Integer Ring
        sage: q = Cl.q()

    Some sample computations::

        sage: p0, p1, p2, d0, d1, d2, w0, w1, w2 = Cl.gens()
        sage: p0 * p1
        psi0*psi1
        sage: p1 * p0
        -psi0*psi1
        sage: p0 * w0 * p1 * d0 * w2
        (1/(q^3-q))*psi1*w2 + (-q/(q^2-1))*psi1*w0^2*w2
        sage: w0^4
        -1/q^2 + ((q^2+1)/q^2)*w0^2

    We construct the homomorphism from `U_q(\mathfrak{sl}_3)` to
    `\mathrm{Cl}(3, 1)` given in (3.17) of [Hayashi1990]_::

        sage: e1 = p0*d1; e2 = p1*d2
        sage: f1 = p1*d0; f2 = p2*d1
        sage: k1 = w0*~w1; k2 = w1*~w2
        sage: k1i = w1*~w0; k2i = w2*~w1
        sage: (e1, e2, f1, f2, k1, k2, k1i, k2i)
        (psi0*psid1, psi1*psid2,
         -psid0*psi1, -psid1*psi2,
         (q^2+1)*w0*w1 - q^2*w0*w1^3, (q^2+1)*w1*w2 - q^2*w1*w2^3,
         (q^2+1)*w0*w1 - q^2*w0^3*w1, (q^2+1)*w1*w2 - q^2*w1^3*w2)

    We check that `k_i` and `k_i^{-1}` are inverses::

        sage: k1 * k1i
        1
        sage: k2 * k2i
        1

    The relations between `e_i`, `f_i`, and `k_i`::

        sage: k1 * f1 == q^-2 * f1 * k1
        True
        sage: k2 * f1 == q^1 * f1 * k2
        True
        sage: k2 * e1 == q^-1 * e1 * k2
        True
        sage: k1 * e1 == q^2 * e1 * k1
        True
        sage: e1 * f1 - f1 * e1 == (k1 - k1i)/(q-q^-1)
        True
        sage: e2 * f1 - f1 * e2
        0

    The `q`-Serre relations::

        sage: e1 * e1 * e2 - (q^1 + q^-1) * e1 * e2 * e1 + e2 * e1 * e1
        0
        sage: f1 * f1 * f2 - (q^1 + q^-1) * f1 * f2 * f1 + f2 * f1 * f1
        0
    """
    @staticmethod
    def __classcall_private__(cls, n, k=1, q=None, F=None):
        r"""
        Standardize input to ensure a unique representation.

        TESTS::

            sage: Cl1 = algebras.QuantumClifford(3)
            sage: q = PolynomialRing(ZZ, 'q').fraction_field().gen()
            sage: Cl2 = algebras.QuantumClifford(3, q=q)
            sage: Cl3 = algebras.QuantumClifford(3, 1, q, q.parent())
            sage: Cl1 is Cl2 and Cl2 is Cl3
            True
        """
        if q is None:
            q = PolynomialRing(ZZ, 'q').fraction_field().gen()
        if F is None:
            F = q.parent()
        q = F(q)
        if F not in Fields():
            raise TypeError("base ring must be a field")
        return super(QuantumCliffordAlgebra, cls).__classcall__(cls, n, k, q, F)

    def __init__(self, n, k, q, F):
        r"""
        Initialize ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(1,2)
            sage: TestSuite(Cl).run(elements=Cl.basis())

            sage: Cl = algebras.QuantumClifford(1,3)
            sage: TestSuite(Cl).run(elements=Cl.basis())  # long time

            sage: Cl = algebras.QuantumClifford(3)  # long time
            sage: elts = Cl.some_elements() + list(Cl.algebra_generators())  # long time
            sage: TestSuite(Cl).run(elements=elts)  # long time

            sage: Cl = algebras.QuantumClifford(2,4)  # long time
            sage: elts = Cl.some_elements() + list(Cl.algebra_generators())  # long time
            sage: TestSuite(Cl).run(elements=elts)  # long time
        """
        self._n = n
        self._k = k
        self._q = q
        self._psi = cartesian_product([(-1,0,1)]*n)
        self._w_poly = PolynomialRing(F, n, 'w')
        indices = [(tuple(psi), tuple(w))
                   for psi in self._psi
                   for w in product(*[list(range((4-2*abs(psi[i]))*k)) for i in range(n)])]
        indices = FiniteEnumeratedSet(indices)

        cat = Algebras(F).FiniteDimensional().WithBasis()
        CombinatorialFreeModule.__init__(self, F, indices, category=cat)
        self._assign_names(self.algebra_generators().keys())

    def _repr_(self):
        r"""
        Return a string representation of ``self``.

        EXAMPLES::

            sage: algebras.QuantumClifford(3)
            Quantum Clifford algebra of rank 3 and twist 1 with q=q over
             Fraction Field of Univariate Polynomial Ring in q over Integer Ring
        """
        return "Quantum Clifford algebra of rank {} and twist {} with q={} over {}".format(
            self._n, self._k, self._q, self.base_ring())

    def _latex_(self):
        r"""
        Return a latex representation of ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3)
            sage: latex(Cl)
            \operatorname{Cl}_{q}(3, 1)
        """
        return "\\operatorname{Cl}_{%s}(%s, %s)" % (self._q, self._n, self._k)

    def _repr_term(self, m):
        r"""
        Return a string representation of the basis element indexed by ``m``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3, 3)
            sage: Cl._repr_term( ((1, 0, -1), (0, -2, 5)) )
            'psi0*psid2*w1^-2*w2^5'
            sage: Cl._repr_term( ((1, 0, -1), (0, 0, 0)) )
            'psi0*psid2'
            sage: Cl._repr_term( ((0, 0, 0), (0, -2, 5)) )
            'w1^-2*w2^5'
            sage: Cl._repr_term( ((0, 0, 0), (0, 0, 0)) )
            '1'

            sage: Cl(5)
            5
        """
        p, v = m
        rp = '*'.join('psi%s'%i if p[i] > 0 else 'psid%s'%i
                      for i in range(self._n) if p[i] != 0)
        gen_str = lambda e: '' if e == 1 else '^%s'%e
        rv = '*'.join('w%s'%i + gen_str(v[i]) for i in range(self._n) if v[i] != 0)
        if rp:
            if rv:
                return rp + '*' + rv
            return rp
        if rv:
            return rv
        return '1'

    def _latex_term(self, m):
        r"""
        Return a latex representation for the basis element indexed by ``m``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3, 3)
            sage: Cl._latex_term( ((1, 0, -1), (0, -2, 5)) )
            '\\psi_{0}\\psi^{\\dagger}_{2}\\omega_{1}^{-2}\\omega_{2}^{5}'
            sage: Cl._latex_term( ((1, 0, -1), (0, 0, 0)) )
            '\\psi_{0}\\psi^{\\dagger}_{2}'
            sage: Cl._latex_term( ((0, 0, 0), (0, -2, 5)) )
            '\\omega_{1}^{-2}\\omega_{2}^{5}'
            sage: Cl._latex_term( ((0, 0, 0), (0, 0, 0)) )
            '1'

            sage: latex(Cl(5))
            5
        """
        p, v = m
        rp = ''.join('\\psi_{%s}'%i if p[i] > 0 else '\\psi^{\\dagger}_{%s}'%i
                     for i in range(self._n) if p[i] != 0)
        gen_str = lambda e: '' if e == 1 else '^{%s}'%e
        rv = ''.join('\\omega_{%s}'%i + gen_str(v[i])
                     for i in range(self._n) if v[i] != 0)
        if not rp and not rv:
            return '1'
        return rp + rv

    def q(self):
        r"""
        Return the `q` of ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3)
            sage: Cl.q()
            q

            sage: Cl = algebras.QuantumClifford(3, q=QQ(-5))
            sage: Cl.q()
            -5
        """
        return self._q

    def twist(self):
        r"""
        Return the twist `k` of ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3, 2)
            sage: Cl.twist()
            2
        """
        return self._k

    def rank(self):
        r"""
        Return the rank `k` of ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3, 2)
            sage: Cl.rank()
            3
        """
        return self._n

    def dimension(self):
        r"""
        Return the dimension of ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3)
            sage: Cl.dimension()
            512

            sage: Cl = algebras.QuantumClifford(4, 2)
            sage: Cl.dimension()
            65536
        """
        return ZZ(8*self._k) ** self._n

    @cached_method
    def algebra_generators(self):
        r"""
        Return the algebra generators of ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3)
            sage: Cl.algebra_generators()
            Finite family {'psi0': psi0, 'psi1': psi1, 'psi2': psi2,
                           'psid0': psid0, 'psid1': psid1, 'psid2': psid2,
                           'w0': w0, 'w1': w1, 'w2': w2}
        """
        one = (0,) * self._n  # one in the corresponding free abelian group
        zero = [0] * self._n
        d = {}
        for i in range(self._n):
            r = list(zero)  # Make a copy
            r[i] = 1
            d['psi%s'%i] = self.monomial( (self._psi(r), one) )
            r[i] = -1
            d['psid%s'%i] = self.monomial( (self._psi(r), one) )
        zero = self._psi(zero)
        for i in range(self._n):
            temp = list(zero)  # Make a copy
            temp[i] = 1
            d['w%s'%i] = self.monomial( (zero, tuple(temp)) )
        return Family(sorted(d), lambda i: d[i])

    @cached_method
    def gens(self):
        r"""
        Return the generators of ``self``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3)
            sage: Cl.gens()
            (psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2)
        """
        return tuple(self.algebra_generators())

    @cached_method
    def one_basis(self):
        r"""
        Return the index of the basis element of `1`.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3)
            sage: Cl.one_basis()
            ((0, 0, 0), (0, 0, 0))
        """
        return (self._psi([0]*self._n), (0,)*self._n)

    @cached_method
    def product_on_basis(self, m1, m2):
        r"""
        Return the product of the basis elements indexed by ``m1`` and ``m2``.

        EXAMPLES::

            sage: Cl = algebras.QuantumClifford(3)
            sage: Cl.inject_variables()
            Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2
            sage: psi0^2  # indirect doctest
            0
            sage: psid0^2
            0
            sage: w0 * psi0
            q*psi0*w0
            sage: w0 * psid0
            1/q*psid0*w0
            sage: w2 * w0
            w0*w2
            sage: w0^4
            -1/q^2 + ((q^2+1)/q^2)*w0^2
        """
        p1, w1 = m1
        p2, w2 = m2
        # Check for \psi_i^2 == 0 and for the dagger version
        if any(p1[i] != 0 and p1[i] == p2[i] for i in range(self._n)):
            return self.zero()

        # \psi_i is represented by a 1 in p1[i] and p2[i]
        # \psi_i^{\dagger} is represented by a -1 in p1[i] and p2[i]

        k = self._k
        q_power = 0
        sign = 1
        pairings = []
        supported = []
        p = [0] * self._n
        # Move w1 * p2 to q^q_power * p2 * w1
        # Also find pairs \psi_i \psi_i^{\dagger} (or vice versa)
        for i in range(self._n):
            if p2[i] != 0:
                supported.append(i)
                q_power += w1[i] * p2[i]
                if p1[i] != 0:
                    # We make pairings 1-based because we cannot distinguish 0 and -0
                    pairings.append((i+1) * p1[i])
            # we know p1[i] != p2[i] if non-zero, so their sum is -1, 0, 1
            p[i] = p1[i] + p2[i]

        supported.append(self._n-1) # To get between the last support and the end
        # Get the sign of moving \psi_i and \psi_i^{\dagger} into position
        for i in reversed(range(1, len(supported))):
            if i % 2 != 0:
                for j in reversed(range(supported[i-1]+1, supported[i]+1)):
                    if p1[j] != 0:
                        sign = (-1)**i * sign

        # We move the pairs \psi_i \psi_i^{\dagger} (or the reverse) to the
        #   end of the \psi part. This does not change the sign because they
        #   move in pairs.
        # We take the products.
        vp = self._w_poly.gens()
        poly = self._w_poly.one()
        q = self._q
        for i in pairings:
            if i < 0:
                i = -i - 1 # Go back to 0-based
                vpik = -q**(2*k) * vp[i]**(3*k) + (1 + q**(2*k)) * vp[i]**k
                poly *= -(vp[i]**k - vpik) / (q**k - q**(-k))
            else:
                i -= 1 # Go back to 0-based
                vpik = -q**(2*k) * vp[i]**(3*k) + (1 + q**(2*k)) * vp[i]**k
                poly *= (q**k * vp[i]**k - q**(-k) * vpik) / (q**k - q**(-k))

        v = list(w1)
        for i in range(self._n):
            v[i] += w2[i]

        # For all \psi_i v_i^k, convert this to either k = 0, 1
        #   and same for \psi_i^{\dagger}
        for i in range(self._n):
            if p[i] > 0 and v[i] != 0:
                q_power -= 2 * k * (v[i] // (2*k))
                v[i] = v[i] % (2*k)
            if p[i] < 0 and v[i] != 0:
                v[i] = v[i] % (2*k)

        poly *= self._w_poly.monomial(*v)
        poly = poly.reduce([vp[i]**(4*k) - (1 + q**(-2*k)) * vp[i]**(2*k) + q**(-2*k)
                            for i in range(self._n)])
        pdict = poly.dict()
        ret = {(self._psi(p), tuple(e)): pdict[e] * q**q_power * sign
               for e in pdict}

        return self._from_dict(ret)

    class Element(CombinatorialFreeModule.Element):
        def inverse(self):
            r"""
            Return the inverse if ``self`` is a basis element.

            EXAMPLES::

                sage: Cl = algebras.QuantumClifford(3)
                sage: Cl.inject_variables()
                Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2
                sage: w0^-1
                (q^2+1)*w0 - q^2*w0^3
                sage: w0^-1 * w0
                1
                sage: w0^-2
                (q^2+1) - q^2*w0^2
                sage: w0^-2 * w0^2
                1
                sage: w0^-2 * w0 == w0^-1
                True
                sage: w = w0 * w1
                sage: w^-1
                (q^4+2*q^2+1)*w0*w1 + (-q^4-q^2)*w0*w1^3
                 + (-q^4-q^2)*w0^3*w1 + q^4*w0^3*w1^3
                sage: w^-1 * w
                1
                sage: w * w^-1
                1

                sage: (w0 + w1)^-1
                Traceback (most recent call last):
                ...
                NotImplementedError: inverse only implemented for basis elements
                sage: (psi0 * w0)^-1
                Traceback (most recent call last):
                ...
                NotImplementedError: inverse only implemented for product of w generators

                sage: Cl = algebras.QuantumClifford(2, 2)
                sage: Cl.inject_variables()
                Defining psi0, psi1, psid0, psid1, w0, w1
                sage: w0^-1
                (q^4+1)*w0^3 - q^4*w0^7
                sage: w0 * w0^-1
                1
            """
            if not self:
                raise ZeroDivisionError
            if len(self) != 1:
                raise NotImplementedError("inverse only implemented for basis elements")
            Cl = self.parent()
            p, w = self.support_of_term()
            if any(p[i] != 0 for i in range(Cl._n)):
                raise NotImplementedError("inverse only implemented for"
                                          " product of w generators")
            poly = Cl._w_poly.monomial(*w)
            wp = Cl._w_poly.gens()
            q = Cl._q
            k = Cl._k
            poly = poly.subs({wi: -q**(2*k) * wi**(4*k-1) + (1 + q**(2*k)) * wi**(2*k-1)
                              for wi in wp})
            poly = poly.reduce([wi**(4*k) - (1 + q**(-2*k)) * wi**(2*k) + q**(-2*k)
                                for wi in wp])
            pdict = poly.dict()
            ret = {(p, tuple(e)): c for e, c in pdict.items()}
            return Cl._from_dict(ret)

        __invert__ = inverse
Example #12
0
        def demazure_character(self, w, f = None):
            r"""
            Returns the Demazure character associated to ``w``.

            INPUT:

            - ``w`` -- an element of the ambient weight lattice
              realization of the crystal, or a reduced word, or an element
              in the associated Weyl group

            OPTIONAL:

            - ``f`` -- a function from the crystal to a module

            This is currently only supported for crystals whose underlying
            weight space is the ambient space.

            The Demazure character is obtained by applying the Demazure operator
            `D_w` (see :meth:`sage.categories.regular_crystals.RegularCrystals.ParentMethods.demazure_operator`)
            to the highest weight element of the classical crystal. The simple
            Demazure operators `D_i` (see
            :meth:`sage.categories.regular_crystals.RegularCrystals.ElementMethods.demazure_operator_simple`)
            do not braid on the level of crystals, but on the level of characters they do.
            That is why it makes sense to input ``w`` either as a weight, a reduced word,
            or as an element of the underlying Weyl group.

            EXAMPLES::

                sage: T = crystals.Tableaux(['A',2], shape = [2,1])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = e[0] + 2*e[2]
                sage: weight.reduced_word()
                [2, 1]
                sage: T.demazure_character(weight)
                x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x1*x3^2

                sage: T = crystals.Tableaux(['A',3],shape=[2,1])
                sage: T.demazure_character([1,2,3])
                x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3
                sage: W = WeylGroup(['A',3])
                sage: w = W.from_reduced_word([1,2,3])
                sage: T.demazure_character(w)
                x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3

                sage: T = crystals.Tableaux(['B',2], shape = [2])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = -2*e[1]
                sage: T.demazure_character(weight)
                x1^2 + x1*x2 + x2^2 + x1 + x2 + x1/x2 + 1/x2 + 1/x2^2 + 1

                sage: T = crystals.Tableaux("B2",shape=[1/2,1/2])
                sage: b2=WeylCharacterRing("B2",base_ring=QQ).ambient()
                sage: T.demazure_character([1,2],f=lambda x:b2(x.weight()))
                b2(-1/2,1/2) + b2(1/2,-1/2) + b2(1/2,1/2)

            REFERENCES:

            - [De1974]_

            - [Ma2009]_
            """
            from sage.misc.misc_c import prod
            from sage.rings.integer_ring import ZZ
            from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
            if hasattr(w, 'reduced_word'):
                word = w.reduced_word()
            else:
                word = w
            n = self.weight_lattice_realization().n
            u = self.algebra(ZZ).sum_of_monomials(self.module_generators)
            u = self.demazure_operator(u, word)
            if f is None:
                x = ['x%s'%i for i in range(1,n+1)]
                P = PolynomialRing(ZZ, x)
                # TODO: use P.linear_combination when PolynomialRing will be a ModulesWithBasis
                return sum((coeff*prod((x[i]**(c.weight()[i]) for i in range(n)), P.one()) for c, coeff in u), P.zero())
            else:
                return sum((coeff*f(c)) for c, coeff in u)
def crossing_divisor(n=3,var="z"):
  poly_ring = PolynomialRing(QQ,n,var)
  div = poly_ring.one()
  for g in poly_ring.gens():
    div *= g
  return div
Example #14
0
class DifferentialPolynomialRing:
    element_class = DifferentialPolynomial

    def __init__(self, base_ring, fibre_names, base_names,
                 max_differential_orders):
        self._fibre_names = tuple(fibre_names)
        self._base_names = tuple(base_names)
        self._max_differential_orders = tuple(max_differential_orders)
        base_dim = len(self._base_names)
        fibre_dim = len(self._fibre_names)
        jet_names = []
        idx_to_name = {}
        for fibre_idx in range(fibre_dim):
            u = self._fibre_names[fibre_idx]
            idx_to_name[(fibre_idx, ) + tuple([0] * base_dim)] = u
            for d in range(1, max_differential_orders[fibre_idx] + 1):
                for multi_index in IntegerVectors(d, base_dim):
                    v = '{}_{}'.format(
                        u, ''.join(self._base_names[i] * multi_index[i]
                                   for i in range(base_dim)))
                    jet_names.append(v)
                    idx_to_name[(fibre_idx, ) + tuple(multi_index)] = v
        self._polynomial_ring = PolynomialRing(
            base_ring, base_names + fibre_names + tuple(jet_names))
        self._idx_to_var = {
            idx: self._polynomial_ring(idx_to_name[idx])
            for idx in idx_to_name
        }
        self._var_to_idx = {
            jet: idx
            for (idx, jet) in self._idx_to_var.items()
        }
        # for conversion:
        base_vars = [var(b) for b in self._base_names]
        symbolic_functions = [
            function(f)(*base_vars) for f in self._fibre_names
        ]
        self._subs_jet_vars = SubstituteJetVariables(symbolic_functions)
        self._subs_tot_ders = SubstituteTotalDerivatives(symbolic_functions)

    def __repr__(self):
        return 'Differential Polynomial Ring in {} over {}'.format(
            ', '.join(map(repr, self._polynomial_ring.gens())),
            self._polynomial_ring.base_ring())

    def _latex_(self):
        return self._polynomial_ring._latex_()

    def base_ring(self):
        return self._polynomial_ring.base_ring()

    def _first_ngens(self, n):
        return tuple(
            self.element_class(self, self._polynomial_ring.gen(i))
            for i in range(n))

    def gens(self):
        return self._first_ngens(self._polynomial_ring.ngens())

    def gen(self, i):
        return self.element_class(self, self._polynomial_ring.gen(i))

    def base_variables(self):
        return self._first_ngens(len(self._base_names))

    def base_dim(self):
        return len(self._base_names)

    def fibre_variable(self, i):
        return self.element_class(
            self, self._polynomial_ring.gen(len(self._base_names) + i))

    def fibre_variables(self):
        base_dim = len(self._base_names)
        fibre_dim = len(self._fibre_names)
        return tuple(
            self.element_class(self, self._polynomial_ring.gen(base_dim + i))
            for i in range(fibre_dim))

    def fibre_dim(self):
        return len(self._fibre_names)

    def jet_variables(self):
        base_dim = len(self._base_names)
        fibre_dim = len(self._fibre_names)
        whole_dim = self._polynomial_ring.ngens()
        return tuple(
            self.element_class(self, self._polynomial_ring.gen(i))
            for i in range(base_dim + fibre_dim, whole_dim))

    def max_differential_orders(self):
        return self._max_differential_orders

    def _single_var_weights(self, u):
        return self._var_to_idx[u][1:]

    def _diff_single_var(self, u, x):
        x_idx = self._polynomial_ring.gens().index(x)
        u_idx = self._var_to_idx[u]
        du_idx = list(u_idx)
        du_idx[1 + x_idx] += 1
        du_idx = tuple(du_idx)
        if du_idx in self._idx_to_var:
            return self._idx_to_var[du_idx]
        else:
            raise ValueError(
                "can't differentiate {} any further with respect to {}".format(
                    u, x))

    def _integrate_single_var(self, u, x):
        x_idx = self._polynomial_ring.gens().index(x)
        u_idx = self._var_to_idx[u]
        if u_idx[1 + x_idx] == 0:
            raise ValueError(
                "can't integrate {} any further with respect to {}".format(
                    u, x))
        iu_idx = list(u_idx)
        iu_idx[1 + x_idx] -= 1
        iu_idx = tuple(iu_idx)
        return self._idx_to_var[iu_idx]

    def __contains__(self, arg):
        if isinstance(arg, self.element_class) and arg.parent() is self:
            return True
        if arg in self._polynomial_ring.base_ring():
            return True
        return False

    def __call__(self, arg):
        if isinstance(arg, self.element_class) and arg.parent() is self:
            return arg
        if is_Expression(arg):
            arg = self._subs_jet_vars(arg)
        return self.element_class(self, self._polynomial_ring(arg))

    def zero(self):
        return self.element_class(self, self._polynomial_ring.zero())

    def one(self):
        return self.element_class(self, self._polynomial_ring.one())

    def homogeneous_monomials(self,
                              fibre_degrees,
                              weights,
                              max_differential_orders=None):
        fibre_vars = self.fibre_variables()
        if not len(fibre_degrees) == len(fibre_vars):
            raise ValueError(
                'length of fibre_degrees vector must match number of fibre variables'
            )
        base_vars = self.base_variables()
        if not len(weights) == len(base_vars):
            raise ValueError(
                'length of weights vector must match number of base variables')
        monomials = []
        fibre_degree = sum(fibre_degrees)
        fibre_indexes = {}
        fibre_idx = 0
        for i in range(len(fibre_degrees)):
            for j in range(fibre_degrees[i]):
                fibre_indexes[fibre_idx] = i
                fibre_idx += 1
        proto = sum([[fibre_vars[i]] * fibre_degrees[i]
                     for i in range(len(fibre_degrees))], [])
        for V in product(*[IntegerVectors(w, fibre_degree) for w in weights]):
            total_differential_order = [0 for i in range(fibre_degree)]
            term = [p for p in proto]
            skip = False
            for j in range(fibre_degree):
                fibre_idx = fibre_indexes[j]
                for i in range(len(base_vars)):
                    if V[i][j] > 0:
                        total_differential_order[j] += V[i][j]
                        if max_differential_orders is not None and total_differential_order[
                                j] > max_differential_orders[fibre_idx]:
                            skip = True
                            break
                        term[j] = term[j].total_derivative(*([base_vars[i]] *
                                                             V[i][j]))
                if skip:
                    break
            if not skip:
                monomials.append(prod(term))
        return monomials
Example #15
0
class QSystem(CombinatorialFreeModule):
    r"""
    A Q-system.

    Let `\mathfrak{g}` be a tamely-laced symmetrizable Kac-Moody algebra
    with index set `I` over a field `k`. Follow the presentation given
    in [HKOTY1999]_, an unrestricted Q-system is a `k`-algebra in infinitely
    many variables `Q^{(a)}_m`, where `a \in I` and `m \in \ZZ_{>0}`,
    that satisifies the relations

    .. MATH::

        \left(Q^{(a)}_m\right)^2 = Q^{(a)}_{m+1} Q^{(a)}_{m-1} +
        \prod_{b \sim a} \prod_{k=0}^{-C_{ab} - 1}
        Q^{(b)}_{\left\lfloor \frac{m C_{ba} - k}{C_{ab}} \right\rfloor},

    with `Q^{(a)}_0 := 1`. Q-systems can be considered as T-systems where
    we forget the spectral parameter `u` and for `\mathfrak{g}` of finite
    type, have a solution given by the characters of Kirillov-Reshetikhin
    modules (again without the spectral parameter) for an affine Kac-Moody
    algebra `\widehat{\mathfrak{g}}` with `\mathfrak{g}` as its classical
    subalgebra. See [KNS2011]_ for more information.

    Q-systems have a natural bases given by polynomials of the
    fundamental representations `Q^{(a)}_1`, for `a \in I`. As such, we
    consider the Q-system as generated by `\{ Q^{(a)}_1 \}_{a \in I}`.

    There is also a level `\ell` restricted Q-system (with unit boundary
    condition) given by setting `Q_{d_a \ell}^{(a)} = 1`, where `d_a`
    are the entries of the symmetrizing matrix for the dual type of
    `\mathfrak{g}`.

    EXAMPLES:

    We begin by constructing a Q-system and doing some basic computations
    in type `A_4`::

        sage: Q = QSystem(QQ, ['A', 4])
        sage: Q.Q(3,1)
        Q^(3)[1]
        sage: Q.Q(1,2)
        Q^(1)[1]^2 - Q^(2)[1]
        sage: Q.Q(3,3)
        -Q^(1)[1]*Q^(3)[1] + Q^(1)[1]*Q^(4)[1]^2 + Q^(2)[1]^2
         - 2*Q^(2)[1]*Q^(3)[1]*Q^(4)[1] + Q^(3)[1]^3
        sage: x = Q.Q(1,1) + Q.Q(2,1); x
        Q^(1)[1] + Q^(2)[1]
        sage: x * x
        Q^(1)[1]^2 + 2*Q^(1)[1]*Q^(2)[1] + Q^(2)[1]^2

    Next we do some basic computations in type `C_4`::

        sage: Q = QSystem(QQ, ['C', 4])
        sage: Q.Q(4,1)
        Q^(4)[1]
        sage: Q.Q(1,2)
        Q^(1)[1]^2 - Q^(2)[1]
        sage: Q.Q(3,3)
        Q^(1)[1]*Q^(4)[1]^2 - 2*Q^(2)[1]*Q^(3)[1]*Q^(4)[1] + Q^(3)[1]^3

    REFERENCES:

    - [HKOTY1999]_
    - [KNS2011]_
    """
    @staticmethod
    def __classcall__(cls, base_ring, cartan_type, level=None):
        """
        Normalize arguments to ensure a unique representation.

        EXAMPLES::

            sage: Q1 = QSystem(QQ, ['A',4])
            sage: Q2 = QSystem(QQ, 'A4')
            sage: Q1 is Q2
            True
        """
        cartan_type = CartanType(cartan_type)
        if not is_tamely_laced(cartan_type):
            raise ValueError("the Cartan type is not tamely-laced")
        return super(QSystem, cls).__classcall__(cls, base_ring, cartan_type, level)

    def __init__(self, base_ring, cartan_type, level):
        """
        Initialize ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',2])
            sage: TestSuite(Q).run()
        """
        self._cartan_type = cartan_type
        self._level = level
        indices = tuple(itertools.product(cartan_type.index_set(), [1]))
        basis = IndexedFreeAbelianMonoid(indices, prefix='Q', bracket=False)
        # This is used to do the reductions
        self._poly = PolynomialRing(ZZ, ['q'+str(i) for i in cartan_type.index_set()])

        category = Algebras(base_ring).Commutative().WithBasis()
        CombinatorialFreeModule.__init__(self, base_ring, basis,
                                         prefix='Q', category=category)

    def _repr_(self):
        r"""
        Return a string representation of ``self``.

        EXAMPLES::

            sage: QSystem(QQ, ['A',4])
            Q-system of type ['A', 4] over Rational Field
        """
        if self._level is not None:
            res = "Restricted level {} ".format(self._level)
        else:
            res = ''
        return "{}Q-system of type {} over {}".format(res, self._cartan_type, self.base_ring())

    def _repr_term(self, t):
        """
        Return a string representation of the basis element indexed by ``t``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: I = Q._indices
            sage: Q._repr_term( I.gen((1,1)) * I.gen((4,1)) )
            'Q^(1)[1]*Q^(4)[1]'
        """
        if len(t) == 0:
            return '1'
        def repr_gen(x):
            ret = 'Q^({})[{}]'.format(*(x[0]))
            if x[1] > 1:
                ret += '^{}'.format(x[1])
            return ret
        return '*'.join(repr_gen(x) for x in t._sorted_items())

    def _latex_term(self, t):
        r"""
        Return a `\LaTeX` representation of the basis element indexed
        by ``t``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: I = Q._indices
            sage: Q._latex_term( I.gen((3,1)) * I.gen((4,1)) )
            'Q^{(3)}_{1} Q^{(4)}_{1}'
        """
        if len(t) == 0:
            return '1'
        def repr_gen(x):
            ret = 'Q^{{({})}}_{{{}}}'.format(*(x[0]))
            if x[1] > 1:
                ret = '\\bigl(' + ret + '\\bigr)^{{{}}}'.format(x[1])
            return ret
        return ' '.join(repr_gen(x) for x in t._sorted_items())

    def _ascii_art_term(self, t):
        """
        Return an ascii art representation of the term indexed by ``t``.

        TESTS::

            sage: Q = QSystem(QQ, ['A',4])
            sage: ascii_art(Q.an_element())
                               2       2       3
                   (1)   ( (1))  ( (2))  ( (3))       (2)
            1 + 2*Q1   + (Q1  ) *(Q1  ) *(Q1  )  + 3*Q1
        """
        from sage.typeset.ascii_art import AsciiArt
        if t == self.one_basis():
            return AsciiArt(["1"])
        ret = AsciiArt("")
        first = True
        for k, exp in t._sorted_items():
            if not first:
                ret += AsciiArt(['*'], baseline=0)
            else:
                first = False
            a,m = k
            var = AsciiArt([" ({})".format(a),
                            "Q{}".format(m)],
                           baseline=0)
            #print var
            #print " "*(len(str(m))+1) + "({})".format(a) + '\n' + "Q{}".format(m)
            if exp > 1:
                var = (AsciiArt(['(','('], baseline=0) + var
                       + AsciiArt([')', ')'], baseline=0))
                var = AsciiArt([" "*len(var) + str(exp)], baseline=-1) * var
            ret += var
        return ret

    def _unicode_art_term(self, t):
        r"""
        Return a unicode art representation of the term indexed by ``t``.

        TESTS::

            sage: Q = QSystem(QQ, ['A',4])
            sage: unicode_art(Q.an_element())
            1 + 2*Q₁⁽¹⁾ + (Q₁⁽¹⁾)²(Q₁⁽²⁾)²(Q₁⁽³⁾)³ + 3*Q₁⁽²⁾
        """
        from sage.typeset.unicode_art import UnicodeArt
        if t == self.one_basis():
            return UnicodeArt(["1"])

        subs = {'0': u'₀', '1': u'₁', '2': u'₂', '3': u'₃', '4': u'₄',
                '5': u'₅', '6': u'₆', '7': u'₇', '8': u'₈', '9': u'₉'}
        sups = {'0': u'⁰', '1': u'¹', '2': u'²', '3': u'³', '4': u'⁴',
                '5': u'⁵', '6': u'⁶', '7': u'⁷', '8': u'⁸', '9': u'⁹'}
        def to_super(x):
            return u''.join(sups[i] for i in str(x))
        def to_sub(x):
            return u''.join(subs[i] for i in str(x))

        ret = UnicodeArt("")
        for k, exp in t._sorted_items():
            a,m = k
            var = UnicodeArt([u"Q" + to_sub(m) + u'⁽' + to_super(a) + u'⁾'], baseline=0)
            if exp > 1:
                var = (UnicodeArt([u'('], baseline=0) + var
                       + UnicodeArt([u')' + to_super(exp)], baseline=0))
            ret += var
        return ret

    def cartan_type(self):
        """
        Return the Cartan type of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.cartan_type()
            ['A', 4]
        """
        return self._cartan_type

    def index_set(self):
        """
        Return the index set of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.index_set()
            (1, 2, 3, 4)
        """
        return self._cartan_type.index_set()

    def level(self):
        """
        Return the restriction level of ``self`` or ``None`` if
        the system is unrestricted.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.level()

            sage: Q = QSystem(QQ, ['A',4], 5)
            sage: Q.level()
            5
        """
        return self._level

    @cached_method
    def one_basis(self):
        """
        Return the basis element indexing `1`.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.one_basis()
            1
            sage: Q.one_basis().parent() is Q._indices
            True
        """
        return self._indices.one()

    @cached_method
    def algebra_generators(self):
        """
        Return the algebra generators of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.algebra_generators()
            Finite family {1: Q^(1)[1], 2: Q^(2)[1], 3: Q^(3)[1], 4: Q^(4)[1]}
        """
        I = self._cartan_type.index_set()
        d = {a: self.Q(a, 1) for a in I}
        return Family(I, d.__getitem__)

    def gens(self):
        """
        Return the generators of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',4])
            sage: Q.gens()
            (Q^(1)[1], Q^(2)[1], Q^(3)[1], Q^(4)[1])
        """
        return tuple(self.algebra_generators())

    def dimension(self):
        """
        Return the dimension of ``self``, which is `\infty`.

        EXAMPLES::

            sage: F = QSystem(QQ, ['A',4])
            sage: F.dimension()
            +Infinity
        """
        return infinity

    def Q(self, a, m):
        r"""
        Return the generator `Q^{(a)}_m` of ``self``.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A', 8])
            sage: Q.Q(2, 1)
            Q^(2)[1]
            sage: Q.Q(6, 2)
            -Q^(5)[1]*Q^(7)[1] + Q^(6)[1]^2
            sage: Q.Q(7, 3)
            -Q^(5)[1]*Q^(7)[1] + Q^(5)[1]*Q^(8)[1]^2 + Q^(6)[1]^2
             - 2*Q^(6)[1]*Q^(7)[1]*Q^(8)[1] + Q^(7)[1]^3
            sage: Q.Q(1, 0)
            1
        """
        if a not in self._cartan_type.index_set():
            raise ValueError("a is not in the index set")
        if m == 0:
            return self.one()
        if self._level:
            t = self._cartan_type.dual().cartan_matrix().symmetrizer()
            if m == t[a] * self._level:
                return self.one()
        if m == 1:
            return self.monomial( self._indices.gen((a,1)) )
        #if self._cartan_type.type() == 'A' and self._level is None:
        #    return self._jacobi_trudy(a, m)
        I = self._cartan_type.index_set()
        p = self._Q_poly(a, m)
        return p.subs({ g: self.Q(I[i], 1) for i,g in enumerate(self._poly.gens()) })

    @cached_method
    def _Q_poly(self, a, m):
        r"""
        Return the element `Q^{(a)}_m` as a polynomial.

        We start with the relation

        .. MATH::

            Q^{(a)}_{m-1}^2 = Q^{(a)}_m Q^{(a)}_{m-2} + \mathcal{Q}_{a,m-1},

        which implies

        .. MATH::

            Q^{(a)}_m = \frac{Q^{(a)}_{m-1}^2 - \mathcal{Q}_{a,m-1}}{
            Q^{(a)}_{m-2}}.

        This becomes our relation used for reducing the Q-system to the
        fundamental representations.

        .. NOTE::

            This helper method is defined in order to use the
            division implemented in polynomial rings.

        EXAMPLES::

            sage: Q = QSystem(QQ, ['A',8])
            sage: Q._Q_poly(1, 2)
            q1^2 - q2
            sage: Q._Q_poly(3, 2)
            q3^2 - q2*q4
            sage: Q._Q_poly(6, 3)
            q6^3 - 2*q5*q6*q7 + q4*q7^2 + q5^2*q8 - q4*q6*q8
        """
        if m == 0 or m == self._level:
            return self._poly.one()
        if m == 1:
            return self._poly.gen(self._cartan_type.index_set().index(a))

        cm = CartanMatrix(self._cartan_type)
        I = self._cartan_type.index_set()
        m -= 1 # So we don't have to do it everywhere

        cur = self._Q_poly(a, m)**2
        i = I.index(a)
        ret = self._poly.one()
        for b in self._cartan_type.dynkin_diagram().neighbors(a):
            j = I.index(b)
            for k in range(-cm[i,j]):
                ret *= self._Q_poly(b, (m * cm[j,i] - k) // cm[i,j])
        cur -= ret
        if m > 1:
            cur //= self._Q_poly(a, m-1)
        return cur

    class Element(CombinatorialFreeModule.Element):
        """
        An element of a Q-system.
        """
        def _mul_(self, x):
            """
            Return the product of ``self`` and ``x``.

            EXAMPLES::

                sage: Q = QSystem(QQ, ['A',8])
                sage: x = Q.Q(1, 2)
                sage: y = Q.Q(3, 2)
                sage: x * y
                -Q^(1)[1]^2*Q^(2)[1]*Q^(4)[1] + Q^(1)[1]^2*Q^(3)[1]^2
                 + Q^(2)[1]^2*Q^(4)[1] - Q^(2)[1]*Q^(3)[1]^2
            """
            return self.parent().sum_of_terms((tl*tr, cl*cr)
                                              for tl,cl in self for tr,cr in x)
Example #16
0
def permanental_minor_polynomial(A, permanent_only=False, var='t', prec=None):
    r"""
    Return the polynomial of the sums of permanental minors of ``A``.

    INPUT:

    - `A` -- a matrix

    - `permanent_only` -- if True, return only the permanent of `A`

    - `var` -- name of the polynomial variable

    - `prec` -- if prec is not None, truncate the polynomial at precision `prec`


    The polynomial of the sums of permanental minors is

    .. MATH::

        \sum_{i=0}^{min(nrows, ncols)} p_i(A) x^i

    where `p_i(A)` is the `i`-th permanental minor of `A` (that can also be
    obtained through the method
    :meth:`~sage.matrix.matrix2.Matrix.permanental_minor` via
    ``A.permanental_minor(i)``).

    The algorithm implemented by that function has been developed by P. Butera
    and M. Pernici, see [ButPer]. Its complexity is `O(2^n m^2 n)` where `m` and
    `n` are the number of rows and columns of `A`.  Moreover, if `A` is a banded
    matrix with width `w`, that is `A_{ij}=0` for `|i - j| > w` and `w < n/2`,
    then the complexity of the algorithm is `O(4^w (w+1) n^2)`.

    INPUT:

    - ``A`` -- matrix

    - ``permanent_only`` -- optional boolean. If ``True``, only the permanent
      is computed (might be faster).

    - ``var`` -- a variable name

    EXAMPLES::

        sage: from sage.matrix.matrix_misc import permanental_minor_polynomial
        sage: m = matrix([[1,1],[1,2]])
        sage: permanental_minor_polynomial(m)
        3*t^2 + 5*t + 1
        sage: permanental_minor_polynomial(m, permanent_only=True)
        3
        sage: permanental_minor_polynomial(m, prec=2)
        5*t + 1

    ::

        sage: M = MatrixSpace(ZZ,4,4)
        sage: A = M([1,0,1,0,1,0,1,0,1,0,10,10,1,0,1,1])
        sage: permanental_minor_polynomial(A)
        84*t^3 + 114*t^2 + 28*t + 1
        sage: [A.permanental_minor(i) for i in range(5)]
        [1, 28, 114, 84, 0]

    An example over `\QQ`::

        sage: M = MatrixSpace(QQ,2,2)
        sage: A = M([1/5,2/7,3/2,4/5])
        sage: permanental_minor_polynomial(A, True)
        103/175

    An example with polynomial coefficients::

        sage: R.<a> = PolynomialRing(ZZ)
        sage: A = MatrixSpace(R,2)([[a,1], [a,a+1]])
        sage: permanental_minor_polynomial(A, True)
        a^2 + 2*a

    A usage of the ``var`` argument::

        sage: m = matrix(ZZ,4,[0,1,2,3,1,2,3,0,2,3,0,1,3,0,1,2])
        sage: permanental_minor_polynomial(m, var='x')
        164*x^4 + 384*x^3 + 172*x^2 + 24*x + 1

    ALGORITHM:

        The permanent `perm(A)` of a `n \times n` matrix `A` is the coefficient
        of the `x_1 x_2 \ldots x_n` monomial in

        .. MATH::

            \prod_{i=1}^n \left( \sum_{j=1}^n A_{ij} x_j \right)

        Evaluating this product one can neglect `x_i^2`, that is `x_i`
        can be considered to be nilpotent of order `2`.

        To formalize this procedure, consider the algebra
        `R = K[\eta_1, \eta_2, \ldots, \eta_n]` where the `\eta_i` are
        commuting, nilpotent of order `2` (i.e. `\eta_i^2 = 0`).
        Formally it is the quotient ring of the polynomial
        ring in `\eta_1, \eta_2, \ldots, \eta_n` quotiented by the ideal
        generated by the `\eta_i^2`.

        We will mostly consider the ring `R[t]` of polynomials over `R`. We
        denote a generic element of `R[t]` by `p(\eta_1, \ldots, \eta_n)` or
        `p(\eta_{i_1}, \ldots, \eta_{i_k})` if we want to emphasize that some
        monomials in the `\eta_i` are missing.

        Introduce an "integration" operation `\langle p \rangle` over `R` and
        `R[t]` consisting in the sum of the coefficients of the non-vanishing
        monomials in `\eta_i` (i.e. the result of setting all variables `\eta_i`
        to `1`). Let us emphasize that this is *not* a morphism of algebras as
        `\langle \eta_1 \rangle^2 = 1` while `\langle \eta_1^2 \rangle = 0`!

        Let us consider an example of computation.
        Let `p_1 = 1 + t \eta_1 + t \eta_2` and
        `p_2 = 1 + t \eta_1 + t \eta_3`. Then

        .. MATH::

            p_1 p_2 = 1 + 2t \eta_1 +
                    t (\eta_2 + \eta_3) +
                    t^2 (\eta_1 \eta_2 + \eta_1 \eta_3 + \eta_2 \eta_3)

        and

        .. MATH::

            \langle p_1 p_2 \rangle = 1 + 4t + 3t^2

        In this formalism, the permanent is just

        .. MATH::

            perm(A) = \langle \prod_{i=1}^n \sum_{j=1}^n A_{ij} \eta_j \rangle

        A useful property of `\langle . \rangle` which makes this algorithm
        efficient for band matrices is the following: let
        `p_1(\eta_1, \ldots, \eta_n)` and `p_2(\eta_j, \ldots, \eta_n)` be
        polynomials in `R[t]` where `j \ge 1`.  Then one has

        .. MATH::

            \langle p_1(\eta_1, \ldots, \eta_n) p_2 \rangle =
            \langle p_1(1, \ldots, 1, \eta_j, \ldots, \eta_n) p_2 \rangle

        where `\eta_1,..,\eta_{j-1}` are replaced by `1` in `p_1`. Informally,
        we can "integrate" these variables *before* performing the product. More
        generally, if a monomial `\eta_i` is missing in one of the terms of a
        product of two terms, then it can be integrated in the other term.

        Now let us consider an `m \times n` matrix with `m \leq n`. The *sum of
        permanental `k`-minors of `A`* is

        .. MATH::

            perm(A, k) = \sum_{r,c} perm(A_{r,c})

        where the sum is over the `k`-subsets `r` of rows and `k`-subsets `c` of
        columns and `A_{r,c}` is the submatrix obtained from `A` by keeping only
        the rows `r` and columns `c`. Of course
        `perm(A, \min(m,n)) = perm(A)` and note that `perm(A,1)` is just the sum
        of all entries of the matrix.

        The generating function of these sums of permanental minors is

        .. MATH::

            g(t) = \left\langle
            \prod_{i=1}^m \left(1 + t \sum_{j=1}^n A_{ij} \eta_j\right)
            \right\rangle

        In fact the `t^k` coefficient of `g(t)` corresponds to choosing
        `k` rows of `A`;  `\eta_i` is associated to the i-th column;
        nilpotency avoids having twice the same column in a product of `A`'s.

        For more details, see the article [ButPer].

        From a technical point of view, the product in
        `K[\eta_1, \ldots, \eta_n][t]` is implemented as a subroutine in
        :func:`prm_mul`. The indices of the rows and columns actually start at
        `0`, so the variables are  `\eta_0, \ldots, \eta_{n-1}`. Polynomials are
        represented in dictionary form: to a variable `\eta_i` is associated
        the key `2^i` (or in Python ``1 << i``). The keys associated to products
        are obtained by considering the development in base `2`: to the monomial
        `\eta_{i_1} \ldots \eta_{i_k}` is associated the key
        `2^{i_1} + \ldots + 2^{i_k}`. So the product `\eta_1 \eta_2` corresponds
        to the key `6 = (110)_2` while `\eta_0 \eta_3` has key `9 = (1001)_2`.
        In particular all operations on monomials are implemented via bitwise
        operations on the keys.

    REFERENCES:

    .. [ButPer] P. Butera and M. Pernici "Sums of permanental minors
       using Grassmann algebra", :arxiv:`1406.5337`
    """
    if permanent_only:
        prec = None
    elif prec is not None:
        prec = int(prec)
        if prec == 0:
            raise ValueError('the argument `prec` must be a positive integer')

    K = PolynomialRing(A.base_ring(), var)
    nrows = A.nrows()
    ncols = A.ncols()
    A = A.rows()
    p = {0: K.one()}
    t = K.gen()
    vars_to_do = range(ncols)
    for i in range(nrows):
        # build the polynomial p1 = 1 + t sum A_{ij} eta_j
        if permanent_only:
            p1 = {}
        else:
            p1 = {0: K.one()}
        a = A[i]   # the i-th row of A
        for j in range(len(a)):
            if a[j]:
                p1[1<<j] = a[j] * t

        # make the product with the preceding polynomials, taking care of
        # variables that can be integrated
        mask_free = 0
        j = 0
        while j < len(vars_to_do):
            jj = vars_to_do[j]
            if all(A[k][jj] == 0 for k in range(i+1, nrows)):
                mask_free += 1 << jj
                vars_to_do.remove(jj)
            else:
                j += 1
        p = prm_mul(p, p1, mask_free, prec)

    if not p:
        return K.zero()

    if len(p) != 1 or 0 not in p:
        raise RuntimeError("Something is wrong! Certainly a problem in the"
                           " algorithm... please contact [email protected]")

    p = p[0]
    return p[min(nrows,ncols)] if permanent_only else p
Example #17
0
class TestLogarithmicDifferentialForm(unittest.TestCase):
    def setUp(self):
        self.poly_ring = PolynomialRing(QQ, "x", 3)
        x = self.x = self.poly_ring.gens()[0]
        y = self.y = self.poly_ring.gens()[1]
        z = self.z = self.poly_ring.gens()[2]
        self.normal_logdf = LogarithmicDifferentialForms(x * y * z)
        self.whitney_logdf = LogarithmicDifferentialForms(x**2 * y - z**2)

    def test_creationA(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        logdf = LogarithmicDifferentialForm(2, [self.x, self.y, self.z],
                                            self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        form[0, 1] = 1 / (y * z)
        form[0, 2] = 1 / (x * z)
        form[1, 2] = 1 / (x * y)
        self.assertEqual(form, logdf.form)

    def test_creation_B(self):
        x = self.whitney_logdf.form_vars[0]
        y = self.whitney_logdf.form_vars[1]
        z = self.whitney_logdf.form_vars[2]
        whitney = x**2 * y - z**2
        logdf = LogarithmicDifferentialForm(2, [self.x, self.y, self.z],
                                            self.whitney_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        form[0, 1] = x / whitney
        form[0, 2] = y / whitney
        form[1, 2] = z / whitney
        self.assertEqual(form, logdf.form)

    def test_creation_0_form(self):
        z = self.normal_logdf.form_vars[2]
        logdf = LogarithmicDifferentialForm(0, [self.x * self.y],
                                            self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 0, 1 / z)
        self.assertEqual(form, logdf.form)

    def test_add(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        logdfA = LogarithmicDifferentialForm(2, [self.x, self.y, self.z],
                                             self.normal_logdf)
        logdfB = LogarithmicDifferentialForm(2, [self.z, self.y, self.x],
                                             self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        form[0, 1] = 1 / (y * z) + 1 / (x * y)
        form[0, 2] = 2 / (x * z)
        form[1, 2] = 1 / (x * y) + 1 / (y * z)
        logdf_sum = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(logdf_sum.equals(logdfA + logdfB))

    def test_sub(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        logdfA = LogarithmicDifferentialForm(2, [self.x, self.y, self.z],
                                             self.normal_logdf)
        logdfB = LogarithmicDifferentialForm(2, [self.z, self.y, self.x],
                                             self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        form[0, 1] = 1 / (y * z) - 1 / (x * y)
        form[0, 2] = 0
        form[1, 2] = 1 / (x * y) - 1 / (y * z)
        logdf_diff = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(logdf_diff.equals(logdfA - logdfB))

    def test_mul(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        logdf = LogarithmicDifferentialForm(2, [self.x, self.y, self.z],
                                            self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        form[0, 1] = -5 / (y * z)
        form[0, 2] = -5 / (x * z)
        form[1, 2] = -5 / (x * y)
        self.assertTrue((logdf * (-5)).equals((-5) * logdf))
        logdf_mul = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue((-5 * logdf).equals(logdf_mul))

    def test_wedge(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        norm = self.x * self.y * self.z
        logdfA = LogarithmicDifferentialForm(
            1, [self.x * norm, self.y * norm, self.z * norm],
            self.normal_logdf)
        logdfB = LogarithmicDifferentialForm(1, [self.z, self.y, self.x],
                                             self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        form[0, 1] = 1 / z - 1 / x
        form[0, 2] = (x / (y * z)) - (z / (x * y))
        form[1, 2] = 1 / z - 1 / x
        logdf_wedge = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(logdf_wedge.equals(logdfA.wedge(logdfB)))

    def test_derivative(self):
        x = self.x
        y = self.y
        z = self.z
        logdf = LogarithmicDifferentialForm(1, [y * z, x * z, x * y],
                                            self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        logdf_der = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(logdf_der.equals(logdf.derivative()))

    def test_unit(self):
        one = LogarithmicDifferentialForm.make_unit(self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 0, 1)
        one_form = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(one.equals(one_form))

    def test_zero_p(self):
        size = [1, 3, 3, 1]
        for s, i in zip(size, range(4)):
            zero = LogarithmicDifferentialForm.make_zero(i, self.normal_logdf)
            zero_vec = [self.normal_logdf.poly_ring.zero() for _ in range(s)]
            self.assertEqual(zero.vec, zero_vec)

    def test_create_from_form(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        xp = self.x
        yp = self.y
        zp = self.z
        logdf = LogarithmicDifferentialForm(
            2, [xp * yp - yp * zp, xp**2 - zp**2, xp * yp - yp * zp],
            self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 2)
        form[0, 1] = 1 / z - 1 / x
        form[0, 2] = (x / (y * z)) - (z / (x * y))
        form[1, 2] = 1 / z - 1 / x
        logdf_form = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(logdf.equals(logdf_form))

    def test_create_from_0_form(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        xp = self.x
        yp = self.y
        zp = self.z
        logdf = LogarithmicDifferentialForm(0, [xp + yp + zp],
                                            self.normal_logdf)
        form = DifferentialForm(self.normal_logdf.form_space, 0,
                                (x + y + z) / (x * y * z))
        logdf_form = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(logdf.equals(logdf_form))

    def test_interior_0_form(self):
        x = self.x
        y = self.y
        z = self.z
        logdf = LogarithmicDifferentialForm(
            0, [x * y - y * z + x**2 - z**2 + x * x - y * z],
            self.normal_logdf)
        int_product = logdf.interior_product()
        zero = LogarithmicDifferentialForm.make_zero(0, self.normal_logdf)
        self.assertTrue(int_product.equals(zero))

    def test_interior_1_form(self):
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        form = DifferentialForm(self.normal_logdf.form_space, 0,
                                (x + y + z) / (x * y * z))
        one = self.poly_ring.one()
        logdf = LogarithmicDifferentialForm(1, [one, one, one],
                                            self.normal_logdf)
        int_product = logdf.interior_product()
        logdf_form = LogarithmicDifferentialForm.create_from_form(
            form, self.normal_logdf)
        self.assertTrue(int_product.equals(logdf_form))

    def test_interior(self):
        xp = self.x
        yp = self.y
        zp = self.z
        logdf = LogarithmicDifferentialForm(
            2, [xp**2, xp + 4 * yp + zp, xp**3 * zp**3], self.whitney_logdf)
        int_product = logdf.interior_product()
        x = self.normal_logdf.form_vars[0]
        y = self.normal_logdf.form_vars[1]
        z = self.normal_logdf.form_vars[2]
        whitney = x**2 * y - z**2
        form = DifferentialForm(self.normal_logdf.form_space, 1)
        form[0] = (-2 * x**2 * y - 2 * x * z - 8 * y * z - 2 * z**2) / whitney
        form[1] = (x**3 - 2 * x**3 * z**4) / whitney
        form[2] = (x**2 + 4 * x * y + x * z + 2 * x**3 * y * z**3) / whitney
        logdf_form = LogarithmicDifferentialForm.create_from_form(
            form, self.whitney_logdf)
        self.assertTrue(int_product.equals(logdf_form))
    def product_on_basis(self, left, right):
        r"""
        Return ``left`` multiplied by ``right`` in ``self``.

        EXAMPLES::

            sage: R = algebras.RationalCherednik(['A',2], 1, 1, QQ)
            sage: a2 = R.algebra_generators()['a2']
            sage: ac1 = R.algebra_generators()['ac1']
            sage: a2 * ac1  # indirect doctest
            a2*ac1
            sage: ac1 * a2
            -I + a2*ac1 - s1 - s2 + 1/2*s1*s2*s1
            sage: x = R.an_element()
            sage: [y * x for y in R.some_elements()]
            [0,
             3*ac1 + 2*s1 + a1,
             9*ac1^2 + 10*I + 6*a1*ac1 + 6*s1 + 3/2*s2 + 3/2*s1*s2*s1 + a1^2,
             3*a1*ac1 + 2*a1*s1 + a1^2,
             3*a2*ac1 + 2*a2*s1 + a1*a2,
             3*s1*ac1 + 2*I - a1*s1,
             3*s2*ac1 + 2*s2*s1 + a1*s2 + a2*s2,
             3*ac1^2 - 2*s1*ac1 + 2*I + a1*ac1 + 2*s1 + 1/2*s2 + 1/2*s1*s2*s1,
             3*ac1*ac2 + 2*s1*ac1 + 2*s1*ac2 - I + a1*ac2 - s1 - s2 + 1/2*s1*s2*s1]
            sage: [x * y for y in R.some_elements()]
            [0,
             3*ac1 + 2*s1 + a1,
             9*ac1^2 + 10*I + 6*a1*ac1 + 6*s1 + 3/2*s2 + 3/2*s1*s2*s1 + a1^2,
             6*I + 3*a1*ac1 + 6*s1 + 3/2*s2 + 3/2*s1*s2*s1 - 2*a1*s1 + a1^2,
             -3*I + 3*a2*ac1 - 3*s1 - 3*s2 + 3/2*s1*s2*s1 + 2*a1*s1 + 2*a2*s1 + a1*a2,
             -3*s1*ac1 + 2*I + a1*s1,
             3*s2*ac1 + 3*s2*ac2 + 2*s1*s2 + a1*s2,
             3*ac1^2 + 2*s1*ac1 + a1*ac1,
             3*ac1*ac2 + 2*s1*ac2 + a1*ac2]
        """
        # Make copies of the internal dictionaries
        dl = dict(left[2]._monomial)
        dr = dict(right[0]._monomial)

        # If there is nothing to commute
        if not dl and not dr:
            return self.monomial((left[0], left[1] * right[1], right[2]))

        R = self.base_ring()
        I = self._cartan_type.index_set()
        P = PolynomialRing(R, 'x', len(I))
        G = P.gens()
        gens_dict = {a:G[i] for i,a in enumerate(I)}
        Q = RootSystem(self._cartan_type).root_lattice()
        alpha = Q.simple_roots()
        alphacheck = Q.simple_coroots()

        def commute_w_hd(w, al): # al is given as a dictionary
            ret = P.one()
            for k in al:
                x = sum(c * gens_dict[i] for i,c in alpha[k].weyl_action(w))
                ret *= x**al[k]
            ret = ret.dict()
            for k in ret:
                yield (self._hd({I[i]: e for i,e in enumerate(k) if e != 0}), ret[k])

        # Do Lac Ra if they are both non-trivial
        if dl and dr:
            il = dl.keys()[0]
            ir = dr.keys()[0]

            # Compute the commutator
            terms = self._product_coroot_root(il, ir)

            # remove the generator from the elements
            dl[il] -= 1
            if dl[il] == 0:
                del dl[il]
            dr[ir] -= 1
            if dr[ir] == 0:
                del dr[ir]

            # We now commute right roots past the left reflections: s Ra = Ra' s
            cur = self._from_dict({ (hd, s*right[1], right[2]): c * cc
                                    for s,c in terms
                                    for hd, cc in commute_w_hd(s, dr) })
            cur = self.monomial( (left[0], left[1], self._h(dl)) ) * cur

            # Add back in the commuted h and hd elements
            rem = self.monomial( (left[0], left[1], self._h(dl)) )
            rem = rem * self.monomial( (self._hd({ir:1}), self._weyl.one(),
                                        self._h({il:1})) )
            rem = rem * self.monomial( (self._hd(dr), right[1], right[2]) )

            return cur + rem

        if dl:
            # We have La Ls Lac Rs Rac,
            #   so we must commute Lac Rs = Rs Lac'
            #   and obtain La (Ls Rs) (Lac' Rac)
            ret = P.one()
            for k in dl:
                x = sum(c * gens_dict[i]
                        for i,c in alphacheck[k].weyl_action(right[1].reduced_word(),
                                                             inverse=True))
                ret *= x**dl[k]
            ret = ret.dict()
            w = left[1]*right[1]
            return self._from_dict({ (left[0], w,
                                      self._h({I[i]: e for i,e in enumerate(k)
                                               if e != 0}) * right[2]
                                     ): ret[k]
                                     for k in ret })

        # Otherwise dr is non-trivial and we have La Ls Ra Rs Rac,
        #   so we must commute Ls Ra = Ra' Ls
        w = left[1]*right[1]
        return self._from_dict({ (left[0] * hd, w, right[2]): c
                                 for hd, c in commute_w_hd(left[1], dr) })
Example #19
0
def permanental_minor_polynomial(A, permanent_only=False, var='t', prec=None):
    r"""
    Return the polynomial of the sums of permanental minors of ``A``.

    INPUT:

    - `A` -- a matrix

    - `permanent_only` -- if True, return only the permanent of `A`

    - `var` -- name of the polynomial variable

    - `prec` -- if prec is not None, truncate the polynomial at precision `prec`


    The polynomial of the sums of permanental minors is

    .. MATH::

        \sum_{i=0}^{min(nrows, ncols)} p_i(A) x^i

    where `p_i(A)` is the `i`-th permanental minor of `A` (that can also be
    obtained through the method
    :meth:`~sage.matrix.matrix2.Matrix.permanental_minor` via
    ``A.permanental_minor(i)``).

    The algorithm implemented by that function has been developed by P. Butera
    and M. Pernici, see [ButPer]. Its complexity is `O(2^n m^2 n)` where `m` and
    `n` are the number of rows and columns of `A`.  Moreover, if `A` is a banded
    matrix with width `w`, that is `A_{ij}=0` for `|i - j| > w` and `w < n/2`,
    then the complexity of the algorithm is `O(4^w (w+1) n^2)`.

    INPUT:

    - ``A`` -- matrix

    - ``permanent_only`` -- optional boolean. If ``True``, only the permanent
      is computed (might be faster).

    - ``var`` -- a variable name

    EXAMPLES::

        sage: from sage.matrix.matrix_misc import permanental_minor_polynomial
        sage: m = matrix([[1,1],[1,2]])
        sage: permanental_minor_polynomial(m)
        3*t^2 + 5*t + 1
        sage: permanental_minor_polynomial(m, permanent_only=True)
        3
        sage: permanental_minor_polynomial(m, prec=2)
        5*t + 1

    ::

        sage: M = MatrixSpace(ZZ,4,4)
        sage: A = M([1,0,1,0,1,0,1,0,1,0,10,10,1,0,1,1])
        sage: permanental_minor_polynomial(A)
        84*t^3 + 114*t^2 + 28*t + 1
        sage: [A.permanental_minor(i) for i in range(5)]
        [1, 28, 114, 84, 0]

    An example over `\QQ`::

        sage: M = MatrixSpace(QQ,2,2)
        sage: A = M([1/5,2/7,3/2,4/5])
        sage: permanental_minor_polynomial(A, True)
        103/175

    An example with polynomial coefficients::

        sage: R.<a> = PolynomialRing(ZZ)
        sage: A = MatrixSpace(R,2)([[a,1], [a,a+1]])
        sage: permanental_minor_polynomial(A, True)
        a^2 + 2*a

    A usage of the ``var`` argument::

        sage: m = matrix(ZZ,4,[0,1,2,3,1,2,3,0,2,3,0,1,3,0,1,2])
        sage: permanental_minor_polynomial(m, var='x')
        164*x^4 + 384*x^3 + 172*x^2 + 24*x + 1

    ALGORITHM:

        The permanent `perm(A)` of a `n \times n` matrix `A` is the coefficient
        of the `x_1 x_2 \ldots x_n` monomial in

        .. MATH::

            \prod_{i=1}^n \left( \sum_{j=1}^n A_{ij} x_j \right)

        Evaluating this product one can neglect `x_i^2`, that is `x_i`
        can be considered to be nilpotent of order `2`.

        To formalize this procedure, consider the algebra
        `R = K[\eta_1, \eta_2, \ldots, \eta_n]` where the `\eta_i` are
        commuting, nilpotent of order `2` (i.e. `\eta_i^2 = 0`).
        Formally it is the quotient ring of the polynomial
        ring in `\eta_1, \eta_2, \ldots, \eta_n` quotiented by the ideal
        generated by the `\eta_i^2`.

        We will mostly consider the ring `R[t]` of polynomials over `R`. We
        denote a generic element of `R[t]` by `p(\eta_1, \ldots, \eta_n)` or
        `p(\eta_{i_1}, \ldots, \eta_{i_k})` if we want to emphasize that some
        monomials in the `\eta_i` are missing.

        Introduce an "integration" operation `\langle p \rangle` over `R` and
        `R[t]` consisting in the sum of the coefficients of the non-vanishing
        monomials in `\eta_i` (i.e. the result of setting all variables `\eta_i`
        to `1`). Let us emphasize that this is *not* a morphism of algebras as
        `\langle \eta_1 \rangle^2 = 1` while `\langle \eta_1^2 \rangle = 0`!

        Let us consider an example of computation.
        Let `p_1 = 1 + t \eta_1 + t \eta_2` and
        `p_2 = 1 + t \eta_1 + t \eta_3`. Then

        .. MATH::

            p_1 p_2 = 1 + 2t \eta_1 +
                    t (\eta_2 + \eta_3) +
                    t^2 (\eta_1 \eta_2 + \eta_1 \eta_3 + \eta_2 \eta_3)

        and

        .. MATH::

            \langle p_1 p_2 \rangle = 1 + 4t + 3t^2

        In this formalism, the permanent is just

        .. MATH::

            perm(A) = \langle \prod_{i=1}^n \sum_{j=1}^n A_{ij} \eta_j \rangle

        A useful property of `\langle . \rangle` which makes this algorithm
        efficient for band matrices is the following: let
        `p_1(\eta_1, \ldots, \eta_n)` and `p_2(\eta_j, \ldots, \eta_n)` be
        polynomials in `R[t]` where `j \ge 1`.  Then one has

        .. MATH::

            \langle p_1(\eta_1, \ldots, \eta_n) p_2 \rangle =
            \langle p_1(1, \ldots, 1, \eta_j, \ldots, \eta_n) p_2 \rangle

        where `\eta_1,..,\eta_{j-1}` are replaced by `1` in `p_1`. Informally,
        we can "integrate" these variables *before* performing the product. More
        generally, if a monomial `\eta_i` is missing in one of the terms of a
        product of two terms, then it can be integrated in the other term.

        Now let us consider an `m \times n` matrix with `m \leq n`. The *sum of
        permanental `k`-minors of `A`* is

        .. MATH::

            perm(A, k) = \sum_{r,c} perm(A_{r,c})

        where the sum is over the `k`-subsets `r` of rows and `k`-subsets `c` of
        columns and `A_{r,c}` is the submatrix obtained from `A` by keeping only
        the rows `r` and columns `c`. Of course
        `perm(A, \min(m,n)) = perm(A)` and note that `perm(A,1)` is just the sum
        of all entries of the matrix.

        The generating function of these sums of permanental minors is

        .. MATH::

            g(t) = \left\langle
            \prod_{i=1}^m \left(1 + t \sum_{j=1}^n A_{ij} \eta_j\right)
            \right\rangle

        In fact the `t^k` coefficient of `g(t)` corresponds to choosing
        `k` rows of `A`;  `\eta_i` is associated to the i-th column;
        nilpotency avoids having twice the same column in a product of `A`'s.

        For more details, see the article [ButPer].

        From a technical point of view, the product in
        `K[\eta_1, \ldots, \eta_n][t]` is implemented as a subroutine in
        :func:`prm_mul`. The indices of the rows and columns actually start at
        `0`, so the variables are  `\eta_0, \ldots, \eta_{n-1}`. Polynomials are
        represented in dictionary form: to a variable `\eta_i` is associated
        the key `2^i` (or in Python ``1 << i``). The keys associated to products
        are obtained by considering the development in base `2`: to the monomial
        `\eta_{i_1} \ldots \eta_{i_k}` is associated the key
        `2^{i_1} + \ldots + 2^{i_k}`. So the product `\eta_1 \eta_2` corresponds
        to the key `6 = (110)_2` while `\eta_0 \eta_3` has key `9 = (1001)_2`.
        In particular all operations on monomials are implemented via bitwise
        operations on the keys.

    REFERENCES:

    .. [ButPer] \P. Butera and M. Pernici "Sums of permanental minors
       using Grassmann algebra", :arxiv:`1406.5337`
    """
    if permanent_only:
        prec = None
    elif prec is not None:
        prec = int(prec)
        if prec == 0:
            raise ValueError('the argument `prec` must be a positive integer')

    K = PolynomialRing(A.base_ring(), var)
    nrows = A.nrows()
    ncols = A.ncols()
    A = A.rows()
    p = {0: K.one()}
    t = K.gen()
    vars_to_do = range(ncols)
    for i in range(nrows):
        # build the polynomial p1 = 1 + t sum A_{ij} eta_j
        if permanent_only:
            p1 = {}
        else:
            p1 = {0: K.one()}
        a = A[i]  # the i-th row of A
        for j in range(len(a)):
            if a[j]:
                p1[1 << j] = a[j] * t

        # make the product with the preceding polynomials, taking care of
        # variables that can be integrated
        mask_free = 0
        j = 0
        while j < len(vars_to_do):
            jj = vars_to_do[j]
            if all(A[k][jj] == 0 for k in range(i + 1, nrows)):
                mask_free += 1 << jj
                vars_to_do.remove(jj)
            else:
                j += 1
        p = prm_mul(p, p1, mask_free, prec)

    if not p:
        return K.zero()

    if len(p) != 1 or 0 not in p:
        raise RuntimeError(
            "Something is wrong! Certainly a problem in the"
            " algorithm... please contact [email protected]")

    p = p[0]
    return p[min(nrows, ncols)] if permanent_only else p
Example #20
0
        def demazure_character(self, w, f=None):
            r"""
            Returns the Demazure character associated to ``w``.

            INPUT:

            - ``w`` -- an element of the ambient weight lattice
              realization of the crystal, or a reduced word, or an element
              in the associated Weyl group

            OPTIONAL:

            - ``f`` -- a function from the crystal to a module

            This is currently only supported for crystals whose underlying
            weight space is the ambient space.

            The Demazure character is obtained by applying the Demazure operator
            `D_w` (see :meth:`sage.categories.regular_crystals.RegularCrystals.ParentMethods.demazure_operator`)
            to the highest weight element of the classical crystal. The simple
            Demazure operators `D_i` (see
            :meth:`sage.categories.regular_crystals.RegularCrystals.ElementMethods.demazure_operator_simple`)
            do not braid on the level of crystals, but on the level of characters they do.
            That is why it makes sense to input ``w`` either as a weight, a reduced word,
            or as an element of the underlying Weyl group.

            EXAMPLES::

                sage: T = crystals.Tableaux(['A',2], shape = [2,1])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = e[0] + 2*e[2]
                sage: weight.reduced_word()
                [2, 1]
                sage: T.demazure_character(weight)
                x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x1*x3^2

                sage: T = crystals.Tableaux(['A',3],shape=[2,1])
                sage: T.demazure_character([1,2,3])
                x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3
                sage: W = WeylGroup(['A',3])
                sage: w = W.from_reduced_word([1,2,3])
                sage: T.demazure_character(w)
                x1^2*x2 + x1*x2^2 + x1^2*x3 + x1*x2*x3 + x2^2*x3

                sage: T = crystals.Tableaux(['B',2], shape = [2])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = -2*e[1]
                sage: T.demazure_character(weight)
                x1^2 + x1*x2 + x2^2 + x1 + x2 + x1/x2 + 1/x2 + 1/x2^2 + 1

                sage: T = crystals.Tableaux("B2",shape=[1/2,1/2])
                sage: b2=WeylCharacterRing("B2",base_ring=QQ).ambient()
                sage: T.demazure_character([1,2],f=lambda x:b2(x.weight()))
                b2(-1/2,1/2) + b2(1/2,-1/2) + b2(1/2,1/2)

            REFERENCES:

            .. [D1974] \M. Demazure, Desingularisation des varietes de Schubert,
               Ann. E. N. S., Vol. 6, (1974), p. 163-172

            .. [M2009] Sarah Mason, An Explicit Construction of Type A Demazure Atoms,
               Journal of Algebraic Combinatorics, Vol. 29, (2009), No. 3, p.295-313.
               :arXiv:`0707.4267`
            """
            from sage.misc.misc_c import prod
            from sage.rings.integer_ring import ZZ
            from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
            if hasattr(w, 'reduced_word'):
                word = w.reduced_word()
            else:
                word = w
            n = self.weight_lattice_realization().n
            u = self.algebra(ZZ).sum_of_monomials(self.module_generators)
            u = self.demazure_operator(u, word)
            if f is None:
                x = ['x%s' % i for i in range(1, n + 1)]
                P = PolynomialRing(ZZ, x)
                # TODO: use P.linear_combination when PolynomialRing will be a ModulesWithBasis
                return sum((coeff * prod((x[i]**(c.weight()[i])
                                          for i in range(n)), P.one())
                            for c, coeff in u), P.zero())
            else:
                return sum((coeff * f(c)) for c, coeff in u)
Example #21
0
        def demazure_character(self, weight, reduced_word = False):
            r"""
            Returns the Demazure character associated to the specified
            weight in the ambient weight lattice.

            INPUT:

                - ``weight`` -- an element of the weight lattice
                  realization of the crystal, or a reduced word
                - ``reduced_word`` -- a boolean (default: ``False``)
                  whether ``weight`` is given as a reduced word

            This is currently only supported for crystals whose
            underlying weight space is the ambient space.

            EXAMPLES::

                sage: T = CrystalOfTableaux(['A',2], shape = [2,1])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = e[0] + 2*e[2]
                sage: weight.reduced_word()
                [2, 1]
                sage: T.demazure_character(weight)
                x1^2*x2 + x1^2*x3 + x1*x2^2 + x1*x2*x3 + x1*x3^2

                sage: T = CrystalOfTableaux(['A',3],shape=[2,1])
                sage: T.demazure_character([1,2,3], reduced_word = True)
                x1^2*x2 + x1^2*x3 + x1*x2^2 + x1*x2*x3 + x2^2*x3

                sage: T = CrystalOfTableaux(['B',2], shape = [2])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = -2*e[1]
                sage: T.demazure_character(weight)
                x1^2 + x1*x2 + x2^2 + x1 + x2 + x1/x2 + 1/x2 + 1/x2^2 + 1

            TODO: detect automatically if weight is a reduced word,
            and remove the (untested!) ``reduced_word`` option.

            REFERENCES::

                .. [D1974] M. Demazure, Desingularisation des varietes de Schubert,
                   Ann. E. N. S., Vol. 6, (1974), p. 163-172

                .. [M2009] Sarah Mason, An Explicit Construction of Type A Demazure Atoms,
                   Journal of Algebraic Combinatorics, Vol. 29, (2009), No. 3, p.295-313
                   (arXiv:0707.4267)

            """
            from sage.misc.misc_c import prod
            from sage.rings.rational_field import QQ
            from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
            if reduced_word:
                word = weight
            else:
                word = weight.reduced_word()
            n = self.weight_lattice_realization().n
            u = list( self.module_generators )
            for i in reversed(word):
                u = u + sum((x.demazure_operator(i, truncated = True) for x in u), [])
            x = ['x%s'%i for i in range(1,n+1)]
            P = PolynomialRing(QQ, x)
            u = [b.weight() for b in u]
            return sum((prod((x[i]**(la[i]) for i in range(n)), P.one()) for la in u), P.zero())
Example #22
0
def crossing_divisor(n=3, var="z"):
    poly_ring = PolynomialRing(QQ, n, var)
    div = poly_ring.one()
    for g in poly_ring.gens():
        div *= g
    return div
    def has_irred_rep(self, n, gen_set=None, restrict=None, force=False):
        """
        Returns `True` if there exists an `n`-dimensional irreducible representation of `self`,
        and `False` otherwise. Of course, this function runs `has_rep(n, restrict)` to verify
        there is a representation in the first place, and returns `False` if not.

        The argument `restrict` may be used equivalenty to its use in `has_rep()`.

        The argument `gen_set` may be set to `'PBW'` or `'pbw'`, if `self` has an algebra basis
        similar to that of a Poincaré-Birkhoff-Witt basis.

        Alternatively, an explicit generating set for the algorithm implemented by this function
        can be given, as a tuple or array of `FreeAlgebraElements`. This is only useful if the
        package cannot reduce the elements of `self`, but they can be reduced in theory.

        Use `force=True` if the function does not recognize the base field as computable, but the
        field is computable.
        """
        if (not force and self.base_field() not in NumberFields
                and self.base_field() not in FiniteFields):
            raise TypeError(
                'Base field must be computable. If %s is computable' %
                self.base_field() + ' then use force=True to bypass this.')

        if n not in ZZ or n < 1:
            raise ValueError('Dimension must be a positive integer.')

        if not self.has_rep(n, restrict): return False
        from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
        from sage.groups.all import SymmetricGroup
        import math

        B = PolynomialRing(self.base_field(), (self.ngens() * n**2 + 1),
                           'x',
                           order='deglex')
        M = MatrixSpace(B, n, sparse=True)

        gen_matrix = list()
        if not isinstance(restrict, (tuple, list)):
            restrict = [None for i in range(self.ngens())]
        if len(restrict) != self.ngens():
            raise ValueError(
                'Length of restrict does not match number of generators.')
        for i in range(self.ngens()):
            ith_gen_matrix = []
            for j in range(n):
                for k in range(n):
                    if restrict[i] == 'upper' and j > k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'lower' and j < k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'diagonal' and j != k:
                        ith_gen_matrix.append(B.zero())
                    else:
                        ith_gen_matrix.append(B.gen(j + (j + 1) * k +
                                                    i * n**2))
            gen_matrix.append(M(ith_gen_matrix))

        relB = list()
        for i in range(self.nrels()):
            relB += self._to_matrix(self.rel(i), M, gen_matrix).list()

        Z = FreeAlgebra(ZZ, 2 * n - 2, 'Y')
        standard_poly = Z(0)
        for s in SymmetricGroup(2 * n - 2).list():
            standard_poly += s.sign() * reduce(
                lambda x, y: x * y, [Z('Y%s' % (i - 1)) for i in s.tuple()])

        if n <= 6 and is_NumberField(self.base_field()): p = 2 * n
        else:
            p = int(
                math.floor(n *
                           math.sqrt(2 * n**2 / float(n - 1) + 1 / float(4)) +
                           n / float(2) - 3))

        if isinstance(gen_set, (tuple, list)):
            try:
                gen_set = [
                    self._to_matrix(elt, M, gen_matrix) for elt in gen_set
                ]
            except (NameError, TypeError) as error:
                print(error)

        if gen_set == None:
            word_gen_set = list(self._create_rep_gen_set(n, p))
            gen_set = [
                self._to_matrix(_to_element(self, [[word, self.one()]]), M,
                                gen_matrix) for word in word_gen_set
            ]

        elif gen_set == 'pbw' or gen_set == 'PBW':
            word_gen_set = list(self._create_pbw_rep_gen_set(n, p))
            gen_set = [
                self._to_matrix(_to_element(self, [[word, self.one()]]), M,
                                gen_matrix) for word in word_gen_set
            ]

        else:
            raise TypeError('Invalid generating set.')

        ordering = [i for i in range(2 * n - 2)]
        max_ordering = [
            len(gen_set) - (2 * n - 2) + i for i in range(2 * n - 2)
        ]
        ordering.insert(0, 0)
        max_ordering.insert(0, len(gen_set))
        rep_exists = False
        z = B.gen(B.ngens() - 1)

        while ordering[0] != max_ordering[0]:
            y = gen_set[ordering[0]].trace_of_product(
                standard_poly.subs({
                    Z('Y%s' % (j - 1)): gen_set[ordering[j]]
                    for j in range(1, 2 * n - 1)
                }))
            radB_test = relB + [B(1) - z * y]
            if B.one() not in B.ideal(radB_test):
                rep_exists = True
                break
            for i in range(2 * n - 2, -1, -1):
                if i != 0 and ordering[i] != max_ordering[i]:
                    ordering[i] += 1
                    break
                elif i == 0:
                    ordering[i] += 1
                    if ordering[i] != max_ordering[i]:
                        for j in range(1, 2 * n - 1):
                            ordering[j] = j - 1

        return rep_exists
Example #24
0
class TestSingularModule(unittest.TestCase):
    def setUp(self):
        self.poly_ring = PolynomialRing(QQ, "x", 3)
        self.x = self.poly_ring.gens()[0]
        self.y = self.poly_ring.gens()[1]
        self.z = self.poly_ring.gens()[2]

    def test_creation(self):
        x = self.x
        y = self.y
        z = self.z
        SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        #Check this did cause an error
        self.assertTrue(True)

    def test_create_ring_str(self):
        x = self.x
        y = self.y
        z = self.z
        sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        self.assertEqual(sm.create_ring_str(), "ring r=0,x(1..3),dp;\n")

    def test_create_module_str(self):
        x = self.x
        y = self.y
        z = self.z
        sm = SingularModule([[x, 2 * y**2, 3 * z**3]])
        self.assertEqual(
            sm.create_module_str("MT"),
            "module MT=[1*x(1)^1,2*x(2)^2,3*x(3)^3];\n groebner(MT);\n")

    def test_contains_zero(self):
        x = self.x
        y = self.y
        z = self.z
        sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        zero = self.poly_ring.zero()
        #Assert we always contain zero
        self.assertTrue(sm.contains([zero, zero, zero]))

    def test_contains_gen(self):
        x = self.x
        y = self.y
        z = self.z
        sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        #Assert we contain our generators
        self.assertTrue(sm.contains([x, y + z, z**3 - 2 * y]))
        self.assertTrue(sm.contains([x, y, z]))

    def test_contains_combination(self):
        x = self.x
        y = self.y
        z = self.z
        sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        #Assert we contain a combination of the generorators
        self.assertTrue(sm.contains([2 * x, 2 * y + z, z**3 - 2 * y + z]))

    def test_contains_fail(self):
        x = self.x
        y = self.y
        z = self.z
        one = self.poly_ring.one()
        sm = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        #Detect that certain vectors are not contained
        self.assertFalse(sm.contains([one, x, z]))
        self.assertFalse(sm.contains([y, y, y]))
        self.assertFalse(sm.contains([z, y, x]))

    def test_contains_fail_multiple(self):
        x = self.x
        y = self.y
        z = self.z
        sm = SingularModule([[x**2, x * y, x * z]])
        #Detect that this is not contianed even though a multiple is
        self.assertFalse(sm.contains([x, y, z]))

    def test_contains_module(self):
        x = self.x
        y = self.y
        z = self.z
        zero = self.poly_ring.zero()
        smA = SingularModule([[x**2, x * y, y * z], [x, y, z]])
        smB = SingularModule([[x**2 + x, x * y + y, y * z + z],
                              [zero, zero, y * z - x * z]])
        self.assertTrue(smA.contains(smB))

    def test_contained_in_ambient(self):
        x = self.x
        y = self.y
        z = self.z
        smA = SingularModule([[x, y**3, z + x], [z, x, x**2]])
        am = smA.ambient_free_module()
        self.assertTrue(am.contains(smA))

    def test_intersect(self):
        x = self.x
        y = self.y
        z = self.z
        smA = SingularModule([[x, y**3, z + x], [z, x, x**2]])
        smB = SingularModule([[z, y, x], [-z, x**2, 4 * y + z]])
        smI = smA.intersection(smB)
        gen_1_1 = x**5 * z + x * y**3 * z**2 + 4 * y**4 * z**2 + y**3 * z**3 + x**3 * y * z - x**3 * z**2 - x**2 * z**3 - x**3 * z - 4 * x**2 * y * z - x**2 * z**2 - x * y * z**2 - y * z**3
        gen_2_1 = x**4 * y**3 * z - x**3 * y**3 * z + x**2 * y**4 * z + 4 * y**5 * z + y**4 * z**2 + x**5 - x**4 * z - x**3 * z**2 - 4 * x**2 * y**2 - 2 * x**2 * y * z - x * y * z**2
        gen_3_1 = x**3 * y**3 * z - x**3 * y * z + 4 * x**2 * y**4 * z + x**2 * y**3 * z**2 + x**6 - 4 * x**3 * y**2 - x**4 * z - x**3 * z**2 - x**3 * z - 4 * x**2 * y * z + 4 * x * y**2 * z - 2 * x**2 * z**2 - 3 * x * y * z**2 + 4 * y**2 * z**2 - x * z**3 + y * z**3
        gens = [[gen_1_1, gen_2_1, gen_3_1]]
        #Test this example
        self.assertEqual(gens, smI.gens)

    def test_intersect_contains(self):
        x = self.x
        y = self.y
        z = self.z
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        sm2 = SingularModule([[x, y**2, z**3]])
        #Assert the intersection is contained in both modules
        gens = (sm1.intersection(sm2)).gens
        for gen in gens:
            self.assertTrue(sm1.contains(gen))
        for gen in gens:
            self.assertTrue(sm2.contains(gen))

    def test_intersection_symetry(self):
        x = self.x
        y = self.y
        z = self.z
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        sm2 = SingularModule([[x, y**2, z**3]])
        sm_1_2 = sm1.intersection(sm2)
        sm_2_1 = sm2.intersection(sm1)
        #Assert that equals is symetric
        self.assertTrue(sm_1_2.equals(sm_2_1))

    def test_reduce_lossless(self):
        x = self.x
        y = self.y
        z = self.z
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        sm2 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        sm2.reduce_generators()
        self.assertTrue(sm1.equals(sm2))

    def test_standard_basis_irredundent(self):
        x = self.x
        y = self.y
        z = self.z
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        std_gens = sm1.standard_basis()
        std_mod = SingularModule(std_gens)
        self.assertTrue(sm1.equals(std_mod))

    def test_equals_A(self):
        x = self.x
        y = self.y
        z = self.z
        #Assert these are equal
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        sm2 = SingularModule([[x, y, z], [x, y + z, z**3 - 2 * y],
                              [x, y, z**2]])
        self.assertTrue(sm1.equals(sm2))

    def test_equals_B(self):
        x = self.x
        y = self.y
        z = self.z
        zero = self.poly_ring.zero()
        #Assert these are equal - from crossing divisor
        sm1 = SingularModule([[x, zero, zero], [zero, y, zero],
                              [zero, zero, z]])
        sm2 = SingularModule([[zero, y, z], [zero, zero, z], [x, zero, zero]])
        self.assertTrue(sm1.equals(sm2))

    def test_equals_not(self):
        x = self.x
        y = self.y
        z = self.z
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        sm2 = SingularModule([[x, y, z]])
        #Assert these are not equal
        self.assertFalse(sm1.equals(sm2))

    def test_ambient_free_module(self):
        x = self.x
        y = self.y
        z = self.z
        one = self.poly_ring.one()
        zero = self.poly_ring.zero()
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2]])
        self.assertEqual(
            sm1.ambient_free_module().gens,
            [[one, zero, zero], [zero, one, zero], [zero, zero, one]])

    def test_is_free_trivial(self):
        free = SingularModule.create_free_module(3, self.poly_ring)
        self.assertTrue(free.is_free())

    def test_is_free(self):
        x = self.x
        y = self.y
        zero = self.poly_ring.zero()
        free = SingularModule([[x, zero, zero], [zero, y, zero],
                               [zero, zero, x**2]])
        self.assertTrue(free.is_free())

    def test_is_free_not(self):
        x = self.x
        y = self.y
        sm1 = SingularModule([[x, x, x], [y, y, y]])
        self.assertFalse(sm1.is_free())

    def test_create_relationA(self):
        x = self.x
        y = self.y
        z = self.z
        one = self.poly_ring.one()
        zero = self.poly_ring.zero()
        relation = [x**2, one + z**2, y]
        ideal = Ideal(self.poly_ring, [x**2 * y - z**2])
        mod = SingularModule.create_from_relation(relation, ideal)
        true_mod = SingularModule([[one, -x**2, x**4], [zero, y, -z**2 - 1],
                                   [zero, z**2, -x**2 * z**2 - x**2],
                                   [zero, zero, x**2 * y - z**2]])
        self.assertTrue(mod.equals(true_mod))

    def test_create_relationB(self):
        #From an error uncovered in log derivations
        x = self.x
        y = self.y
        z = self.z
        zero = self.poly_ring.zero()
        relation = [y * z, x * z, x * y]
        ideal = Ideal(self.poly_ring, [x * y * z])
        mod = SingularModule.create_from_relation(relation, ideal)
        true_mod = SingularModule([[x, zero, zero], [zero, y, zero],
                                   [zero, zero, z]])
        self.assertTrue(mod.equals(true_mod))

    def test_create_relation_satisfy_A(self):
        x = self.x
        y = self.y
        z = self.z
        zero = self.poly_ring.zero()
        relation = [x + x**4 - y, (y + z)**3, -2 * y + self.poly_ring.one()]
        ideal = Ideal(self.poly_ring, [x**2 * y - z**2])
        mod = SingularModule.create_from_relation(relation, ideal)
        for gen in mod.gens:
            sum = zero
            for g, rel in zip(gen, relation):
                sum = sum + g * rel
        self.assertTrue(sum in ideal)

    def test_create_relation_satisfy_B(self):
        x = self.x
        y = self.y
        z = self.z
        one = self.poly_ring.one()
        zero = self.poly_ring.zero()
        relation = [x**2, one + z**2, y]
        ideal = Ideal(self.poly_ring, [x**2 * y - z**2])
        mod = SingularModule.create_from_relation(relation, ideal)
        for gen in mod.gens:
            sum = zero
            for g, rel in zip(gen, relation):
                sum = sum + g * rel
        self.assertTrue(sum in ideal)

    def test_create_relations_satisfy(self):
        x = self.x
        y = self.y
        z = self.z
        zero = self.poly_ring.zero()
        relations = [[x**2, z * y], [y**2, -x]]
        ideals = [
            Ideal(self.poly_ring, [x**2 * y - z**2]),
            Ideal(self.poly_ring, [x * y - z])
        ]
        mod = SingularModule.create_from_relations(relations, ideals)
        for relation, ideal in zip(relations, ideals):
            for gen in mod.gens:
                sum = zero
                for g, rel in zip(gen, relation):
                    sum = sum + g * rel
            self.assertTrue(sum in ideal)

    def test_create_singular_free(self):
        free_matrix = "MM[1,1]=1\nMM[1,2]=0\nMM[1,3]=0\n"
        free_matrix = free_matrix + "MM[2,1]=0\nMM[2,2]=1\nMM[2,3]=0\n"
        free_matrix = free_matrix + "MM[3,1]=0\nMM[3,2]=0\nMM[3,3]=1\n"
        free_c = SingularModule.create_from_singular_matrix(
            self.poly_ring, free_matrix)
        free = SingularModule.create_free_module(3, self.poly_ring)
        self.assertTrue(free.equals(free_c))

    def test_create_singular(self):
        out = "mat_inter[1,1]=0\n"
        out = out + "mat_inter[1,2]=0\n"
        out = out + "mat_inter[1,3]=x(1)*x(2)*x(3)\n"
        out = out + "mat_inter[2,1]=x(2)\n"
        out = out + "mat_inter[2,2]=0\n"
        out = out + "mat_inter[2,3]=0\n"
        out = out + "mat_inter[3,1]=-x(3)\n"
        out = out + "mat_inter[3,2]=x(3)\n"
        out = out + "mat_inter[3,3]=0\n"
        mod = SingularModule.create_from_singular_matrix(
            self.poly_ring, out, "mat_inter")
        x = self.x
        y = self.y
        z = self.z
        zero = self.poly_ring.zero()
        mod_true = [[zero, y, -z], [zero, zero, z], [x * y * z, zero, zero]]
        self.assertEqual(mod.gens, mod_true)

    def test_lift_zero(self):
        x = self.x
        y = self.y
        z = self.z
        one = self.poly_ring.one()
        zero = self.poly_ring.zero()
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z],
                              [x, y, z**2], [one, x, y]])
        vec = sm1.lift([zero, zero, zero])
        self.assertEqual(vec, [zero for _ in range(4)])

    def test_lift_satisfy(self):
        x = self.x
        y = self.y
        z = self.z
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        vec = sm1.lift([
            x**2 + x * z + x, x * y + x * z + y * z + y,
            z**3 * x - 2 * y * x + z**2 + z
        ])
        self.assertEqual(vec[0] * x + vec[1] * x, x**2 + x * z + x)
        self.assertEqual(vec[0] * (y + z) + vec[1] * y,
                         x * y + x * z + y * z + y)
        self.assertEqual(vec[0] * (z**3 - 2 * y) + vec[1] * z,
                         z**3 * x - 2 * y * x + z**2 + z)

    def test_lift_linear(self):
        x = self.x
        y = self.y
        z = self.z
        sm1 = SingularModule([[x, y + z, z**3 - 2 * y], [x, y, z]])
        vec = sm1.lift([3 * x, 3 * y + z, z**3 - 2 * y + 2 * z], True)
        self.assertEqual(vec, [1, 2])
class TestLogarithmicDifferentialForm(unittest.TestCase):
    
  def setUp(self):
    self.poly_ring = PolynomialRing(QQ,"x",3);
    x = self.x = self.poly_ring.gens()[0];
    y = self.y = self.poly_ring.gens()[1];
    z = self.z = self.poly_ring.gens()[2];
    self.normal_logdf = LogarithmicDifferentialForms(x*y*z)
    self.whitney_logdf = LogarithmicDifferentialForms(x**2*y-z**2)

  def test_creationA(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    logdf = LogarithmicDifferentialForm(2,[self.x,self.y,self.z],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    form[0,1] = 1/(y*z)
    form[0,2] = 1/(x*z)
    form[1,2] = 1/(x*y)
    self.assertEqual(form,logdf.form)

  def test_creation_B(self):
    x = self.whitney_logdf.form_vars[0]
    y = self.whitney_logdf.form_vars[1]
    z = self.whitney_logdf.form_vars[2]
    whitney = x**2*y-z**2
    logdf = LogarithmicDifferentialForm(2,[self.x,self.y,self.z],self.whitney_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    form[0,1] = x/whitney
    form[0,2] = y/whitney
    form[1,2] = z/whitney
    self.assertEqual(form,logdf.form)

  def test_creation_0_form(self):
    z = self.normal_logdf.form_vars[2]
    logdf = LogarithmicDifferentialForm(0,[self.x*self.y],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,0,1/z)
    self.assertEqual(form,logdf.form)

  def test_add(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    logdfA = LogarithmicDifferentialForm(2,[self.x,self.y,self.z],self.normal_logdf)
    logdfB = LogarithmicDifferentialForm(2,[self.z,self.y,self.x],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    form[0,1] = 1/(y*z) + 1/(x*y)
    form[0,2] = 2/(x*z)
    form[1,2] = 1/(x*y) + 1/(y*z)
    logdf_sum = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(logdf_sum.equals(logdfA+logdfB))

  def test_sub(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    logdfA = LogarithmicDifferentialForm(2,[self.x,self.y,self.z],self.normal_logdf)
    logdfB = LogarithmicDifferentialForm(2,[self.z,self.y,self.x],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    form[0,1] = 1/(y*z) - 1/(x*y)
    form[0,2] = 0
    form[1,2] = 1/(x*y) - 1/(y*z)
    logdf_diff = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(logdf_diff.equals(logdfA-logdfB))

  def test_mul(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    logdf = LogarithmicDifferentialForm(2,[self.x,self.y,self.z],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    form[0,1] = -5/(y*z)
    form[0,2] = -5/(x*z)
    form[1,2] = -5/(x*y)
    self.assertTrue((logdf*(-5)).equals((-5)*logdf))
    logdf_mul = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue((-5*logdf).equals(logdf_mul))

  def test_wedge(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    norm = self.x*self.y*self.z
    logdfA = LogarithmicDifferentialForm(1,[self.x*norm,self.y*norm,self.z*norm],self.normal_logdf)
    logdfB = LogarithmicDifferentialForm(1,[self.z,self.y,self.x],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    form[0,1] = 1/z - 1/x
    form[0,2] = (x/(y*z)) - (z/(x*y))
    form[1,2] = 1/z - 1/x
    logdf_wedge = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(logdf_wedge.equals(logdfA.wedge(logdfB)))

  def test_derivative(self):
    x = self.x
    y = self.y
    z = self.z
    logdf = LogarithmicDifferentialForm(1,[y*z,x*z,x*y],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    logdf_der = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(logdf_der.equals(logdf.derivative()))

  def test_unit(self):
    one = LogarithmicDifferentialForm.make_unit(self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,0,1)
    one_form = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(one.equals(one_form))

  def test_zero_p(self):
    size = [1,3,3,1]
    for s,i in zip(size,range(4)):
      zero = LogarithmicDifferentialForm.make_zero(i,self.normal_logdf)
      zero_vec = [self.normal_logdf.poly_ring.zero() for _ in range(s)]
      self.assertEqual(zero.vec,zero_vec)

  def test_create_from_form(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    xp = self.x
    yp = self.y
    zp = self.z
    logdf = LogarithmicDifferentialForm(2,[xp*yp-yp*zp,xp**2-zp**2,xp*yp-yp*zp],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,2)
    form[0,1] = 1/z - 1/x
    form[0,2] = (x/(y*z)) - (z/(x*y))
    form[1,2] = 1/z - 1/x
    logdf_form = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(logdf.equals(logdf_form))

  def test_create_from_0_form(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    xp = self.x
    yp = self.y
    zp = self.z
    logdf = LogarithmicDifferentialForm(0,[xp+yp+zp],self.normal_logdf)
    form = DifferentialForm(self.normal_logdf.form_space,0,(x+y+z)/(x*y*z))
    logdf_form = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(logdf.equals(logdf_form))


  def test_interior_0_form(self):
    x = self.x
    y = self.y
    z = self.z
    logdf = LogarithmicDifferentialForm(0,[x*y-y*z+x**2-z**2+x*x-y*z],self.normal_logdf)
    int_product = logdf.interior_product()
    zero = LogarithmicDifferentialForm.make_zero(0,self.normal_logdf)
    self.assertTrue(int_product.equals(zero))

  def test_interior_1_form(self):
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    form = DifferentialForm(self.normal_logdf.form_space,0,(x+y+z)/(x*y*z))
    one = self.poly_ring.one()
    logdf = LogarithmicDifferentialForm(1,[one,one,one],self.normal_logdf)
    int_product = logdf.interior_product()
    logdf_form = LogarithmicDifferentialForm.create_from_form(form,self.normal_logdf)
    self.assertTrue(int_product.equals(logdf_form))

  def test_interior(self):
    xp = self.x
    yp = self.y
    zp = self.z
    logdf = LogarithmicDifferentialForm(2,[xp**2,xp+4*yp+zp,xp**3*zp**3],self.whitney_logdf)
    int_product = logdf.interior_product()
    x = self.normal_logdf.form_vars[0]
    y = self.normal_logdf.form_vars[1]
    z = self.normal_logdf.form_vars[2]
    whitney = x**2*y - z**2
    form = DifferentialForm(self.normal_logdf.form_space,1)
    form[0] = (-2*x**2*y-2*x*z-8*y*z-2*z**2)/whitney
    form[1] = (x**3-2*x**3*z**4)/whitney
    form[2] = (x**2+4*x*y+x*z+2*x**3*y*z**3)/whitney
    logdf_form = LogarithmicDifferentialForm.create_from_form(form,self.whitney_logdf)
    self.assertTrue(int_product.equals(logdf_form))
Example #26
0
        def demazure_character(self, weight, reduced_word = False):
            r"""
            Returns the Demazure character associated to the specified
            weight in the ambient weight lattice.

            INPUT:

                - ``weight`` -- an element of the weight lattice
                  realization of the crystal, or a reduced word
                - ``reduced_word`` -- a boolean (default: ``False``)
                  whether ``weight`` is given as a reduced word

            This is currently only supported for crystals whose
            underlying weight space is the ambient space.

            EXAMPLES::

                sage: T = CrystalOfTableaux(['A',2], shape = [2,1])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = e[0] + 2*e[2]
                sage: weight.reduced_word()
                [2, 1]
                sage: T.demazure_character(weight)
                x1^2*x2 + x1^2*x3 + x1*x2^2 + x1*x2*x3 + x1*x3^2

                sage: T = CrystalOfTableaux(['A',3],shape=[2,1])
                sage: T.demazure_character([1,2,3], reduced_word = True)
                x1^2*x2 + x1^2*x3 + x1*x2^2 + x1*x2*x3 + x2^2*x3

                sage: T = CrystalOfTableaux(['B',2], shape = [2])
                sage: e = T.weight_lattice_realization().basis()
                sage: weight = -2*e[1]
                sage: T.demazure_character(weight)
                x1^2 + x1*x2 + x2^2 + x1 + x2 + x1/x2 + 1/x2 + 1/x2^2 + 1

            TODO: detect automatically if weight is a reduced word,
            and remove the (untested!) ``reduced_word`` option.

            REFERENCES::

                .. [D1974] M. Demazure, Desingularisation des varietes de Schubert,
                   Ann. E. N. S., Vol. 6, (1974), p. 163-172

                .. [M2009] Sarah Mason, An Explicit Construction of Type A Demazure Atoms,
                   Journal of Algebraic Combinatorics, Vol. 29, (2009), No. 3, p.295-313
                   (arXiv:0707.4267)

            """
            from sage.misc.misc_c import prod
            from sage.rings.rational_field import QQ
            from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
            if reduced_word:
                word = weight
            else:
                word = weight.reduced_word()
            n = self.weight_lattice_realization().n
            u = list( self.module_generators )
            for i in reversed(word):
                u = u + sum((x.demazure_operator(i, truncated = True) for x in u), [])
            x = ['x%s'%i for i in range(1,n+1)]
            P = PolynomialRing(QQ, x)
            u = [b.weight() for b in u]
            return sum((prod((x[i]**(la[i]) for i in range(n)), P.one()) for la in u), P.zero())
class TestLogartihmicDifferentialForms(unittest.TestCase):

  def setUp(self):
    self.poly_ring = PolynomialRing(QQ,"x",3);
    self.x = self.poly_ring.gens()[0];
    self.y = self.poly_ring.gens()[1];
    self.z = self.poly_ring.gens()[2];
    self.vars = var('x,y,z')
    
  def test_p_forms_crossing_ngens(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    self.assertEqual(len(logdf.p_form_generators(0)),1)
    self.assertEqual(len(logdf.p_form_generators(1)),3)
    self.assertEqual(len(logdf.p_form_generators(2)),3)
    self.assertEqual(len(logdf.p_form_generators(3)),1)
    
  def test_0_modules_crossing_ngens(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    crossing_0_module = SingularModule([[crossing]])
    self.assertTrue(crossing_0_module.equals(logdf.p_module(0)))
    
  def test_1_modules_crossing_ngens(self):
    x = self.x
    y = self.y
    z = self.z
    zero = self.poly_ring.zero()
    crossing = x*y*z
    logdf = LogarithmicDifferentialForms(crossing)
    crossing_1_module = SingularModule([[y*z,zero,zero],[zero,x*z,zero],[zero,zero,x*y]])
    self.assertTrue(crossing_1_module.equals(logdf.p_module(1)))
    
  def test_2_modules_crossing_ngens(self):
    x = self.x
    y = self.y
    z = self.z
    zero = self.poly_ring.zero()
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    crossing_2_module = SingularModule([[z,zero,zero],[zero,y,zero],[zero,zero,x]])
    self.assertTrue(crossing_2_module.equals(logdf.p_module(2)))
    
  def test_3_modules_crossing_ngens(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    crossing_3_module = SingularModule([[self.poly_ring.one()]])
    self.assertTrue(crossing_3_module.equals(logdf.p_module(3)))

  def test_p_module_n_crossing(self):
    #Make sure this doesnt throw an error - fix bug
    for i in range(4,5):
      p_ring = PolynomialRing(QQ,i,"z")
      crossing = p_ring.one()
      for g in p_ring.gens():
        crossing *= g
      logdf = LogarithmicDifferentialForms(crossing)
      logdf.p_module(i-1)

  def test_complement_complex_crossing(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    complex = logdf.chain_complex("complement")
    complex_size = {}
    for i,c in complex.iteritems():
      complex_size[i] = len(c)
    self.assertEqual(complex_size,{0:1,1:3,2:3,3:1})

  def test_complement_complex_whitney(self):
    whitney = self.x**2*self.y - self.z**2
    logdf = LogarithmicDifferentialForms(whitney)
    complex = logdf.chain_complex("complement")
    complex_size = {}
    for i,c in complex.iteritems():
      complex_size[i] = len(c)
    self.assertEqual(complex_size,{0:1,1:1,2:0,3:0})

  def test_equi_complex_crossing(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    complex = logdf.chain_complex("equivarient")
    complex_size = {}
    for i,c in complex.iteritems():
      complex_size[i] = len(c)
    self.assertEqual(complex_size,{0:1,1:3,2:4,3:4})

  def test_equi_complex_whitney(self):
    whitney = self.x**2*self.y - self.z**2
    logdf = LogarithmicDifferentialForms(whitney)
    complex = logdf.chain_complex("equivarient")
    complex_size = {}
    for i,c in complex.iteritems():
      complex_size[i] = len(c)
    self.assertEqual(complex_size,{0:1,1:1,2:1,3:1})

  def test_complement_homology_crossing(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    homology = logdf.homology("complement")
    homology_size = {}
    for i,c in homology.iteritems():
      homology_size[i] = len(c)
    self.assertEqual(homology_size,{0:1,1:3,2:3,3:1})

  def test_equi_homology_whitney(self):
    whitney = self.x**2*self.y-self.z**2
    logdf = LogarithmicDifferentialForms(whitney)
    homology = logdf.homology("equivarient")
    homology_size = {}
    for i,c in homology.iteritems():
      homology_size[i] = len(c)
    self.assertEqual(homology_size,{0:1,1:0,2:0,3:0})

  def test_relative_complex_0_crossing(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    homology = logdf.chain_complex("relative",None,0)
    homology_size = {}
    for i,c in homology.iteritems():
      homology_size[i] = len(c)
    self.assertEqual(homology_size,{0:1,1:2,2:1,3:0})

  def test_relative_complex_0_whitney(self):
    whitney = self.x**2*self.y-self.z**2
    logdf = LogarithmicDifferentialForms(whitney)
    homology = logdf.chain_complex("relative",None,0)
    homology_size = {}
    for i,c in homology.iteritems():
      homology_size[i] = len(c)
    self.assertEqual(homology_size,{0:1,1:0,2:0,3:0})

  def test_relative_homology_0_crossing(self):
    crossing = self.x*self.y*self.z
    logdf = LogarithmicDifferentialForms(crossing)
    homology = logdf.homology("relative",0)
    homology_size = {}
    for i,c in homology.iteritems():
      homology_size[i] = len(c)
    self.assertEqual(homology_size,{0:1,1:2,2:1,3:0})

  def test_relative_homology_0_whitney(self):
    whitney = self.x**2*self.y-self.z**2
    logdf = LogarithmicDifferentialForms(whitney)
    homology = logdf.homology("relative",0)
    homology_size = {}
    for i,c in homology.iteritems():
      homology_size[i] = len(c)
    self.assertEqual(homology_size,{0:1,1:0,2:0,3:0})