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
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
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)
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)})
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
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
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
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)
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
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) })
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
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)
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())
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
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))
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})