From cfbccd68e0b49362f8b47be0718ed4df929655a7 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Thu, 17 Jul 2025 13:37:07 -0500 Subject: [PATCH 01/11] implement matrix coefficients --- src/sage/algebras/meson.build | 1 + src/sage/algebras/vertex_operators.py | 144 ++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 src/sage/algebras/vertex_operators.py diff --git a/src/sage/algebras/meson.build b/src/sage/algebras/meson.build index bad93c41abf..79b77b583b9 100644 --- a/src/sage/algebras/meson.build +++ b/src/sage/algebras/meson.build @@ -38,6 +38,7 @@ py.install_sources( 'shuffle_algebra.py', 'splitting_algebra.py', 'tensor_algebra.py', + 'vertex_operators.py', 'weyl_algebra.py', 'yangian.py', 'yokonuma_hecke_algebra.py', diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py new file mode 100644 index 00000000000..e6dc010c129 --- /dev/null +++ b/src/sage/algebras/vertex_operators.py @@ -0,0 +1,144 @@ +r""" +Currently just a sandbox for experimenting with vertex operator code +before a more permanent implementation + +TESTS:: + sage: from sage.algebras.vertex_operators import * + sage: R = SymmetricFunctions(QQ); p = R.p(); s = R.s() + sage: P = p.completion() + sage: F = FermionicFockSpace(s) + sage: x = 3*F(([3,2,1],0)); x + 3*s[]*|[3, 2, 1], 0> + sage: J = Current() + sage: J.act(3, x) + (-3*s[])*|[1, 1, 1], 0> + (-3*s[])*|[3], 0> + sage: deg(x) + 6 +""" + +from sage.categories.cartesian_product import cartesian_product +from sage.categories.action import Action +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.partition import Partitions +from sage.combinat.sf.sf import SymmetricFunctions +from sage.misc.cachefunc import cached_function +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ + +class FermionicFockSpace(CombinatorialFreeModule): + + def __init__(self, R): + I = cartesian_product((Partitions(), ZZ)) + CombinatorialFreeModule.__init__(self, R, I, prefix='', bracket='') + + def _repr_term(self, m): + return '|' + str(m[0]) + ', ' + str(m[1]) + '>' + +class Current(Action): + """ + The action of the current operators `J_i` on the Fermionic Fock space. + For `i \neq 0`, we can view `J_i` as moving a particle by `-i` in all possible ways. + Equivalently, viewed on the bosonic side, the action of `J_i` can be calculated + using the Murnaghan-Nakayam rule. + + EXAMPLES:: + + sage: from sage.algebras.vertex_operators import * + sage: R = SymmetricFunctions(QQ); s = R.s() + sage: F = FermionicFockSpace(s) + sage: x = F(([3,2], 0)) + sage: J = Current() + sage: J.act(-3, x) + s[]*|[3, 2, 1, 1, 1], 0> + (-s[])*|[3, 2, 2, 1], 0> + (-s[])*|[4, 4], 0> + + s[]*|[6, 2], 0> + sage: J.act(1,x) + s[]*|[2, 2], 0> + s[]*|[3, 1], 0> + + """ + def __init__(self): + self._R = SymmetricFunctions(QQ) + self._s = self._R.s() + self._p = self._R.p() + self.fockspace = FermionicFockSpace(self._s) + super().__init__(ZZ, self.fockspace) + def _act_(self, g, x): + res = self.fockspace.zero() + if g > 0: # \partial p_k + for (m,c) in x.monomial_coefficients().items(): + par = self._s(m[0]) + for (m2,c2) in (par.skew_by(self._p[g])).monomial_coefficients().items(): + res += c*c2*self.fockspace((m2, m[1])) + return res + elif g < 0: #-k*p_k + for (m,c) in x.monomial_coefficients().items(): + par = self._s(m[0]) + for (m2, c2) in (self._s(self._p[-g]*par)).monomial_coefficients().items(): + res += c*c2*self.fockspace((m2, m[1])) + return res + else: #multiply by charge + return sum(m[1]*c*self.fockspace(m) for (m,c) in x.monomial_coefficients().items()) + def act_by_partition(self,lam, sign, x): + for i in lam: + # TODO: Make this more efficient by acting by the whole partition at once + x = self._act_(i*sign, x) + return x + +@cached_function +def Hamiltonian(): + r""" + + TESTS:: + + sage: from sage.algebras.vertex_operators import * + sage: H = Hamiltonian() + sage: H.truncate(3) + p[] + p[1] + (1/2*p[1,1]+1/2*p[2]) + """ + p = SymmetricFunctions(QQ).p() + P = p.completion() + return P(lambda n: p[n]/n, valuation=1).exp() + +def act_by_H(x): + r""" + Computes the action of H on an element ``x`` of the Fermionic Fock Space + + TESTS:: + + sage: from sage.algebras.vertex_operators import * + sage: R = SymmetricFunctions(QQ); s = R.s() + sage: F = FermionicFockSpace(s) + sage: act_by_H(F(([2,1],0))) + (s[2,1])*|[], 0> + (s[1,1]+s[2])*|[1], 0> + s[1]*|[1, 1], 0> + s[1]*|[2], 0> + + s[]*|[2, 1], 0> + """ + J = Current() + H = Hamiltonian().truncate(deg(x) + 1).symmetric_function() + p = J._p + + res = x.parent().zero() + for (hm, hc) in H.monomial_coefficients().items(): + res += hc*p(hm)*J.act_by_partition(hm,1, x) + return res + +def H_mat_coeff(lam, mu): + r""" + + TESTS:: + + sage: from sage.algebras.vertex_operators import * + sage: lam = [3,2,1]; s = SymmetricFunctions(QQ).s() + sage: all(H_mat_coeff(lam, mu) == s(lam).skew_by(s(mu)) for mu in Partitions(outer=lam)) + True + """ + R = SymmetricFunctions(QQ) + s = R.s() + F = FermionicFockSpace(s) + return act_by_H(F((lam, 0))).coefficient((mu,0)) + +# degree of element of F +def deg(x): + return max((m[0].size() for (m,_) in x.monomial_coefficients().items()), default=0) + + + + From ae424574897f3ee22afa1b7dd33052d76ba7d059 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Mon, 21 Jul 2025 18:16:16 -0500 Subject: [PATCH 02/11] some progress implementing vertex operators on bosonic fock space --- src/sage/algebras/vertex_operators.py | 94 +++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index e6dc010c129..34081fc155c 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -19,14 +19,28 @@ from sage.categories.cartesian_product import cartesian_product from sage.categories.action import Action from sage.combinat.free_module import CombinatorialFreeModule -from sage.combinat.partition import Partitions +from sage.combinat.partition import Partitions, Partition from sage.combinat.sf.sf import SymmetricFunctions +from sage.data_structures.stream import Stream_function, Stream_cauchy_compose +from sage.functions.other import factorial from sage.misc.cachefunc import cached_function from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ +from sage.sets.non_negative_integers import NonNegativeIntegers class FermionicFockSpace(CombinatorialFreeModule): + r""" + A model of the Fermionic Fock space `\mathcal{H}_F`. As a vector space, `\mathcal{H}_F` + has basis given by pairs `(\lambda, n)` where `\lambda` is a partition, and `n` is an integer. + + EXAMPLES:: + sage: from sage.algberas.vertex_operators import * + sage: F = FermionicFockSpace(QQ) + sage: F.an_element() + |[], 0> + 4*|[], 1> + 2*|[1], 0> + """ def __init__(self, R): I = cartesian_product((Partitions(), ZZ)) CombinatorialFreeModule.__init__(self, R, I, prefix='', bracket='') @@ -83,11 +97,13 @@ def act_by_partition(self,lam, sign, x): x = self._act_(i*sign, x) return x + + @cached_function def Hamiltonian(): r""" - TESTS:: + EXAMPLES:: sage: from sage.algebras.vertex_operators import * sage: H = Hamiltonian() @@ -102,7 +118,7 @@ def act_by_H(x): r""" Computes the action of H on an element ``x`` of the Fermionic Fock Space - TESTS:: + EXAMPLES:: sage: from sage.algebras.vertex_operators import * sage: R = SymmetricFunctions(QQ); s = R.s() @@ -123,7 +139,7 @@ def act_by_H(x): def H_mat_coeff(lam, mu): r""" - TESTS:: + EXAMPLES:: sage: from sage.algebras.vertex_operators import * sage: lam = [3,2,1]; s = SymmetricFunctions(QQ).s() @@ -141,4 +157,74 @@ def deg(x): +class HalfVertexOperator(): + def __init__(self,f): + self._stream = Stream_cauchy_compose( + Stream_function(lambda n: ZZ(1)/factorial(n), False, 0), + Stream_function(f, False, 1), + False + ) + def __getitem__(self, i): + if i in NonNegativeIntegers(): + return self._stream[i] + raise ValueError("Invalid input") +class VertexOperator(Action): + """ + + TESTS:: + + sage: from sage.algebras.vertex_operators import * + sage: W. = DifferentialWeylAlgebra(QQ, n=oo); dx = W.differentials() + sage: CreationOp = VertexOperator(lambda n: x[n], lambda n: -dx[n]/n) + sage: AnnihilOp = VertexOperator(lambda n: -x[n], lambda n: dx[n]/n) + sage: B. = LaurentPolynomialRing(SymmetricFunctions(QQ).s()) + sage: CreationOp.act(0,B.one()) + s[]*w + sage: AnnihilOp.act(0, B.one()) + s[]*w^-1 + """ + def __init__(self, pos, neg, cutoff = lambda x: min(x.degree(), 1)): + self.pos = HalfVertexOperator(pos) + self.neg = HalfVertexOperator(neg) + # TODO: make names an optional argument + self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names = ('z')) + self.cutoff = cutoff + + super().__init__(self, ZZ, self.fockspace) + + def _act_(self, i,x): + res = 0 + # for each component of constant charge, compute the action + for (charge, fn) in x.monomial_coefficients().items(): + print(charge, fn) + res += self.fockspace.gen()**(charge+1)*self._act_on_sym(i, fn, charge) + + + return res + + def _act_on_sym(self, i, x, c): + p = self.fockspace.base_ring().symmetric_function_ring().p() + op = 0 + for j in range(self.cutoff(x)): + if i + j + c < 0: + continue + print(f"{i+j+c}, +={self.pos[i+j+c]} -={self.neg[j]}") + op += self.pos[i + j + c]*self.neg[j] + print(op) + if op in self.fockspace.base_ring().base_ring(): + return op*x + res = 0 + for (m, c) in op.monomial_coefficients().items(): + print(m, c) + par1 = self._weyl_to_par(m[0].dict()) + par2 = self._weyl_to_par(m[1].dict()) + for (m2, c2) in x.monomial_coefficients().items(): + print(m2,c2) + res += c*c2*(p(m2).skew_by(p(par2)) * p(par1)) + return res + def _weyl_to_par(self,m): + res = [] + for i in m: + res += [i]*m[i] + return Partition(sorted(res, reverse=True)) \ No newline at end of file From 67417a91a711d163b88e90590f3c717ce6cf4fcc Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Tue, 22 Jul 2025 13:11:57 -0500 Subject: [PATCH 03/11] make creation operators work correctly and add tests --- src/sage/algebras/vertex_operators.py | 57 ++++++++++++++++++--------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index 34081fc155c..35ae36ceea3 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -24,6 +24,7 @@ from sage.data_structures.stream import Stream_function, Stream_cauchy_compose from sage.functions.other import factorial from sage.misc.cachefunc import cached_function +from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ @@ -36,7 +37,7 @@ class FermionicFockSpace(CombinatorialFreeModule): EXAMPLES:: - sage: from sage.algberas.vertex_operators import * + sage: from sage.algebras.vertex_operators import * sage: F = FermionicFockSpace(QQ) sage: F.an_element() |[], 0> + 4*|[], 1> + 2*|[1], 0> @@ -171,34 +172,56 @@ def __getitem__(self, i): class VertexOperator(Action): """ - - TESTS:: + The action of a Vertex Operator on the Bosonic Fock space. + + INPUT: + + - ``pos`` -- function taking in nonnegative integers indexing the positive + half of the vertex operator + - ``neg`` -- function taking in nonnegative integers indexing the negative + half of the vertex operator + - ``cutoff`` -- function taking in symmetric functions which determines how + far to expand the vertex operator (default: ``lambda x: max(x.degree(), 1)) + - ``dcharge`` -- integer (default: ``1``) indicating how this vertex operator + should change the charge of an element + - ``fockspace``-- the space that the vertex operators are acting on. + + EXAMPLES:: sage: from sage.algebras.vertex_operators import * sage: W. = DifferentialWeylAlgebra(QQ, n=oo); dx = W.differentials() - sage: CreationOp = VertexOperator(lambda n: x[n], lambda n: -dx[n]/n) - sage: AnnihilOp = VertexOperator(lambda n: -x[n], lambda n: dx[n]/n) sage: B. = LaurentPolynomialRing(SymmetricFunctions(QQ).s()) + sage: CreationOp = VertexOperator(lambda n: x[n], lambda n: -dx[n]/n, dcharge=1, fockspace=B) + sage: AnnihilOp = VertexOperator(lambda n: -x[n], lambda n: dx[n]/n, dcharge=-1, fockspace=B) sage: CreationOp.act(0,B.one()) s[]*w + sage: CreationOp.act(1, B.one()) + s[1]*w + sage: t1 = CreationOp.act(-2, B.gen()^(-3)); t1 + s[1]*w^-2 + sage: t2 = CreationOp.act(0, t1); t2 + s[2, 1]*w^-1 + sage: t3 = CreationOp.act(3, t2); t3 + s[4, 2, 1] sage: AnnihilOp.act(0, B.one()) s[]*w^-1 """ - def __init__(self, pos, neg, cutoff = lambda x: min(x.degree(), 1)): + def __init__(self, pos, neg, cutoff = lambda x: max(x.degree(), 1), dcharge=1, fockspace=None): self.pos = HalfVertexOperator(pos) self.neg = HalfVertexOperator(neg) - # TODO: make names an optional argument - self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names = ('z')) + if fockspace is None: + self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names = ('w')) + else: #TODO: check input is correct type + self.fockspace = fockspace self.cutoff = cutoff - - super().__init__(self, ZZ, self.fockspace) + self.dcharge = dcharge + super().__init__(ZZ, self.fockspace) def _act_(self, i,x): res = 0 # for each component of constant charge, compute the action for (charge, fn) in x.monomial_coefficients().items(): - print(charge, fn) - res += self.fockspace.gen()**(charge+1)*self._act_on_sym(i, fn, charge) + res += self.fockspace.gen()**(charge+self.dcharge)*self._act_on_sym(i, fn, -charge) return res @@ -206,25 +229,21 @@ def _act_(self, i,x): def _act_on_sym(self, i, x, c): p = self.fockspace.base_ring().symmetric_function_ring().p() op = 0 - for j in range(self.cutoff(x)): + for j in range(self.cutoff(x)+1): if i + j + c < 0: continue - print(f"{i+j+c}, +={self.pos[i+j+c]} -={self.neg[j]}") op += self.pos[i + j + c]*self.neg[j] - print(op) if op in self.fockspace.base_ring().base_ring(): return op*x res = 0 for (m, c) in op.monomial_coefficients().items(): - print(m, c) par1 = self._weyl_to_par(m[0].dict()) par2 = self._weyl_to_par(m[1].dict()) for (m2, c2) in x.monomial_coefficients().items(): - print(m2,c2) - res += c*c2*(p(m2).skew_by(p(par2)) * p(par1)) + res += c*c2*(self.fockspace.base_ring()(m2).skew_by(p(par2)) * p(par1)/(prod(par1))) return res def _weyl_to_par(self,m): res = [] for i in m: - res += [i]*m[i] + res += [i]*int(m[i]) return Partition(sorted(res, reverse=True)) \ No newline at end of file From 203e119ff53ba5667c3a963d6403d0ff6f879666 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Wed, 23 Jul 2025 12:43:09 -0500 Subject: [PATCH 04/11] progress subclassing VertexOperator for specific kinds of vertex operators --- src/sage/algebras/vertex_operators.py | 104 +++++++++++++++++++------- 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index 35ae36ceea3..e8958195ec9 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -16,6 +16,7 @@ 6 """ +from sage.algebras.weyl_algebra import DifferentialWeylAlgebra from sage.categories.cartesian_product import cartesian_product from sage.categories.action import Action from sage.combinat.free_module import CombinatorialFreeModule @@ -25,6 +26,7 @@ from sage.functions.other import factorial from sage.misc.cachefunc import cached_function from sage.misc.misc_c import prod +from sage.rings.infinity import PlusInfinity from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ @@ -186,25 +188,6 @@ class VertexOperator(Action): should change the charge of an element - ``fockspace``-- the space that the vertex operators are acting on. - EXAMPLES:: - - sage: from sage.algebras.vertex_operators import * - sage: W. = DifferentialWeylAlgebra(QQ, n=oo); dx = W.differentials() - sage: B. = LaurentPolynomialRing(SymmetricFunctions(QQ).s()) - sage: CreationOp = VertexOperator(lambda n: x[n], lambda n: -dx[n]/n, dcharge=1, fockspace=B) - sage: AnnihilOp = VertexOperator(lambda n: -x[n], lambda n: dx[n]/n, dcharge=-1, fockspace=B) - sage: CreationOp.act(0,B.one()) - s[]*w - sage: CreationOp.act(1, B.one()) - s[1]*w - sage: t1 = CreationOp.act(-2, B.gen()^(-3)); t1 - s[1]*w^-2 - sage: t2 = CreationOp.act(0, t1); t2 - s[2, 1]*w^-1 - sage: t3 = CreationOp.act(3, t2); t3 - s[4, 2, 1] - sage: AnnihilOp.act(0, B.one()) - s[]*w^-1 """ def __init__(self, pos, neg, cutoff = lambda x: max(x.degree(), 1), dcharge=1, fockspace=None): self.pos = HalfVertexOperator(pos) @@ -221,18 +204,13 @@ def _act_(self, i,x): res = 0 # for each component of constant charge, compute the action for (charge, fn) in x.monomial_coefficients().items(): - res += self.fockspace.gen()**(charge+self.dcharge)*self._act_on_sym(i, fn, -charge) - + res += self.fockspace.gen()**(charge+self.dcharge)*self._act_on_sym(i, fn, charge) return res def _act_on_sym(self, i, x, c): p = self.fockspace.base_ring().symmetric_function_ring().p() - op = 0 - for j in range(self.cutoff(x)+1): - if i + j + c < 0: - continue - op += self.pos[i + j + c]*self.neg[j] + op = self._get_operator(i, x, c) if op in self.fockspace.base_ring().base_ring(): return op*x res = 0 @@ -242,8 +220,80 @@ def _act_on_sym(self, i, x, c): for (m2, c2) in x.monomial_coefficients().items(): res += c*c2*(self.fockspace.base_ring()(m2).skew_by(p(par2)) * p(par1)/(prod(par1))) return res + def _get_operator(self, i, x, c): + raise NotImplementedError("Use a subclass of VertexOperator") + def _weyl_to_par(self,m): res = [] for i in m: res += [i]*int(m[i]) - return Partition(sorted(res, reverse=True)) \ No newline at end of file + return Partition(sorted(res, reverse=True)) + + +class CreationOperator(VertexOperator): + """ + + EXAMPLES:: + + sage: from sage.algebras.vertex_operators import * + sage: B. = LaurentPolynomialRing(SymmetricFunctions(QQ).s()) + sage: Cre = CreationOperator(B) + sage: Cre.act(-1, B.one()) + 0 + sage: Cre.act(0, B.one()) + s[]*w + sage: Cre.act(1, w) + s[]*w^2 + sage: Cre.act(0, w^-1) + s[1] + sage: t = Cre.act(1, Cre.act(-1, w^-2)); t + s[2, 1] + sage: Cre.act(3, t) + s[3, 2, 1]*w + """ + def __init__(self, fockspace): + self.weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) + self.x, self.dx = self.weyl_algebra.gens() + super().__init__(lambda n: self.x[n], lambda n: -self.dx[n]/n, dcharge=1, fockspace=fockspace) + + def _get_operator(self, i, x, c): + op = 0 + for j in range(self.cutoff(x)+1): + if i + j - c < 0: + continue + op += self.pos[i + j - c]*self.neg[j] + return op + + +# TODO: Fix mysterious off by one error causing tests to fail. +class AnnihilationOperator(VertexOperator): + """ + + EXAMPLES:: + + sage: from sage.algebras.vertex_operators import * + sage: B. = LaurentPolynomialRing(SymmetricFunctions(QQ).s()) + sage: Ann = AnnihilationOperator(B) + sage: Ann.act(0, B.one()) # known bug + 0 + sage: Ann.act(-1, B.one()) # known bug + s[]*w^-1 + sage: Ann.act(-2, w^-1) # known bug + s[]*w^-2 + sage: Ann.act(-1, w) # known bug + s[1] + sage: Ann.act(-2, Ann.act(0, w^2)) # known bug + s[2, 1] + """ + def __init__(self, fockspace): + self.weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) + self.x, self.dx = self.weyl_algebra.gens() + super().__init__(lambda n: -self.x[n], lambda n: self.dx[n]/n, dcharge=-1, fockspace=fockspace) + + def _get_operator(self, i, x, c): + op = 0 + for j in range(self.cutoff(x)+1): + if -i + j + c < 0: + continue + op += self.pos[-i + j + c]*self.neg[j] + return op \ No newline at end of file From 019b4b364d791f67edd744d8dd3940836695aa03 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Thu, 24 Jul 2025 14:10:34 -0500 Subject: [PATCH 05/11] fix issue with Annihilation Operator + clean up progress --- src/doc/en/reference/algebras/index.rst | 1 + src/sage/algebras/vertex_operators.py | 215 ++++++++++++++++-------- 2 files changed, 147 insertions(+), 69 deletions(-) diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index c996e12171e..56159914e8a 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -71,6 +71,7 @@ Named associative algebras sage/algebras/steenrod/steenrod_algebra_bases sage/algebras/steenrod/steenrod_algebra_misc sage/algebras/steenrod/steenrod_algebra_mult + sage/algebras/vertex_operators sage/algebras/weyl_algebra sage/algebras/yangian diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index e8958195ec9..3a31550c727 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -32,10 +32,11 @@ from sage.rings.rational_field import QQ from sage.sets.non_negative_integers import NonNegativeIntegers + class FermionicFockSpace(CombinatorialFreeModule): r""" A model of the Fermionic Fock space `\mathcal{H}_F`. As a vector space, `\mathcal{H}_F` - has basis given by pairs `(\lambda, n)` where `\lambda` is a partition, and `n` is an integer. + has basis given by pairs `(\lambda, n)` where `\lambda` is a partition, and `n` is an integer. EXAMPLES:: @@ -45,18 +46,23 @@ class FermionicFockSpace(CombinatorialFreeModule): |[], 0> + 4*|[], 1> + 2*|[1], 0> """ def __init__(self, R): - I = cartesian_product((Partitions(), ZZ)) - CombinatorialFreeModule.__init__(self, R, I, prefix='', bracket='') + index_set = cartesian_product((Partitions(), ZZ)) + CombinatorialFreeModule.__init__(self, R, index_set, prefix='', bracket='') def _repr_term(self, m): return '|' + str(m[0]) + ', ' + str(m[1]) + '>' + +def BosonicFockSpace(names=('w',)): + return LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=names) + + class Current(Action): - """ + r""" The action of the current operators `J_i` on the Fermionic Fock space. - For `i \neq 0`, we can view `J_i` as moving a particle by `-i` in all possible ways. + For `i \neq 0`, we can view `J_i` as moving a particle by `-i` in all possible ways. Equivalently, viewed on the bosonic side, the action of `J_i` can be calculated - using the Murnaghan-Nakayam rule. + using the Murnaghan-Nakayam rule. EXAMPLES:: @@ -78,33 +84,39 @@ def __init__(self): self._p = self._R.p() self.fockspace = FermionicFockSpace(self._s) super().__init__(ZZ, self.fockspace) + def _act_(self, g, x): res = self.fockspace.zero() - if g > 0: # \partial p_k - for (m,c) in x.monomial_coefficients().items(): + if g > 0: # \partial p_k + for (m, c) in x.monomial_coefficients().items(): par = self._s(m[0]) - for (m2,c2) in (par.skew_by(self._p[g])).monomial_coefficients().items(): + for (m2, c2) in (par.skew_by(self._p[g])).monomial_coefficients().items(): res += c*c2*self.fockspace((m2, m[1])) return res - elif g < 0: #-k*p_k - for (m,c) in x.monomial_coefficients().items(): + elif g < 0: # -k*p_k + for (m, c) in x.monomial_coefficients().items(): par = self._s(m[0]) for (m2, c2) in (self._s(self._p[-g]*par)).monomial_coefficients().items(): - res += c*c2*self.fockspace((m2, m[1])) + res += c*c2*self.fockspace((m2, m[1])) return res - else: #multiply by charge - return sum(m[1]*c*self.fockspace(m) for (m,c) in x.monomial_coefficients().items()) - def act_by_partition(self,lam, sign, x): + else: # multiply by charge + return sum(m[1]*c*self.fockspace(m) for (m, c) in x.monomial_coefficients().items()) + + def act_by_partition(self, lam, sign, x): for i in lam: # TODO: Make this more efficient by acting by the whole partition at once - x = self._act_(i*sign, x) + x = self._act_(i*sign, x) return x - @cached_function def Hamiltonian(): r""" + Returns the (exponentiated) Hamiltonian `\exp( \sum_{j \geq 1} p_i J_i)` + where `J_i` is a current operator and `p_i` is a powersum symmetric function. + + This operator acts on the Fermionic Fock space, and its matrix coefficients are + the skew Schur functions. EXAMPLES:: @@ -117,10 +129,13 @@ def Hamiltonian(): P = p.completion() return P(lambda n: p[n]/n, valuation=1).exp() + def act_by_H(x): r""" Computes the action of H on an element ``x`` of the Fermionic Fock Space + If `x = |\lambda, n\rangle`, then `H\cdot x = \sum_{\mu} s_{\lambda/\mu}|\mu, n\rangle`. + EXAMPLES:: sage: from sage.algebras.vertex_operators import * @@ -136,14 +151,16 @@ def act_by_H(x): res = x.parent().zero() for (hm, hc) in H.monomial_coefficients().items(): - res += hc*p(hm)*J.act_by_partition(hm,1, x) + res += hc*p(hm)*J.act_by_partition(hm, 1, x) return res + def H_mat_coeff(lam, mu): r""" - + Compute the coefficient of `|\mu, n\rangle` in `H\cdot |\lambda, n\rangle`. + EXAMPLES:: - + sage: from sage.algebras.vertex_operators import * sage: lam = [3,2,1]; s = SymmetricFunctions(QQ).s() sage: all(H_mat_coeff(lam, mu) == s(lam).skew_by(s(mu)) for mu in Partitions(outer=lam)) @@ -152,87 +169,111 @@ def H_mat_coeff(lam, mu): R = SymmetricFunctions(QQ) s = R.s() F = FermionicFockSpace(s) - return act_by_H(F((lam, 0))).coefficient((mu,0)) + return act_by_H(F((lam, 0))).coefficient((mu, 0)) + # degree of element of F def deg(x): - return max((m[0].size() for (m,_) in x.monomial_coefficients().items()), default=0) - + return max((m[0].size() for (m, _) in x.monomial_coefficients().items()), default=0) class HalfVertexOperator(): - def __init__(self,f): + def __init__(self, f): self._stream = Stream_cauchy_compose( Stream_function(lambda n: ZZ(1)/factorial(n), False, 0), - Stream_function(f, False, 1), + Stream_function(f, False, 1), False ) + def __getitem__(self, i): if i in NonNegativeIntegers(): return self._stream[i] raise ValueError("Invalid input") + class VertexOperator(Action): - """ + r""" The action of a Vertex Operator on the Bosonic Fock space. INPUT: - ``pos`` -- function taking in nonnegative integers indexing the positive - half of the vertex operator + half of the vertex operator - ``neg`` -- function taking in nonnegative integers indexing the negative - half of the vertex operator + half of the vertex operator - ``cutoff`` -- function taking in symmetric functions which determines how - far to expand the vertex operator (default: ``lambda x: max(x.degree(), 1)) + far to expand the vertex operator (default: ``lambda x: max(x.degree(), 1)``) - ``dcharge`` -- integer (default: ``1``) indicating how this vertex operator - should change the charge of an element + should change the charge of an element - ``fockspace``-- the space that the vertex operators are acting on. """ - def __init__(self, pos, neg, cutoff = lambda x: max(x.degree(), 1), dcharge=1, fockspace=None): + def __init__(self, pos, neg, cutoff=lambda x: max(x.degree(), 1), dcharge=1, fockspace=None): self.pos = HalfVertexOperator(pos) self.neg = HalfVertexOperator(neg) if fockspace is None: - self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names = ('w')) - else: #TODO: check input is correct type + self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=('w')) + else: # TODO: check input is correct type self.fockspace = fockspace self.cutoff = cutoff self.dcharge = dcharge super().__init__(ZZ, self.fockspace) - - def _act_(self, i,x): + + def _act_(self, i, x): + r""" + Action of the ``i``'th Fourier mode of ``self`` on element ``x`` of + ``self.fockspace`` + """ res = 0 - # for each component of constant charge, compute the action + # for each component of constant charge, compute the action for (charge, fn) in x.monomial_coefficients().items(): res += self.fockspace.gen()**(charge+self.dcharge)*self._act_on_sym(i, fn, charge) return res def _act_on_sym(self, i, x, c): + r""" + Action of the ``i``'th Fourier mode of ``self`` on a homoegeneous element + of ``self.fockspace``. + """ p = self.fockspace.base_ring().symmetric_function_ring().p() - op = self._get_operator(i, x, c) + op = self._get_operator(i, self.cutoff(x), c) if op in self.fockspace.base_ring().base_ring(): return op*x res = 0 for (m, c) in op.monomial_coefficients().items(): - par1 = self._weyl_to_par(m[0].dict()) - par2 = self._weyl_to_par(m[1].dict()) - for (m2, c2) in x.monomial_coefficients().items(): + par1 = self._d_to_par(m[0].dict()) + par2 = self._d_to_par(m[1].dict()) + for (m2, c2) in x.monomial_coefficients().items(): res += c*c2*(self.fockspace.base_ring()(m2).skew_by(p(par2)) * p(par1)/(prod(par1))) return res + def _get_operator(self, i, x, c): raise NotImplementedError("Use a subclass of VertexOperator") - - def _weyl_to_par(self,m): + + def _d_to_par(self, m): + """ + compute the partition corresponding to a dictionary ``m`` where the value + of key ``i`` in ``m`` corresponds to the number of parts of size ``i``. + + EXAMPLES:: + + sage: from sage.algebras.vertex_operators import * + sage: B = BosonicFockSpace() + sage: V = CreationOperator(B) + sage: V._d_to_par({1:3, 4:1}) + [4, 1, 1, 1] + + """ res = [] for i in m: res += [i]*int(m[i]) return Partition(sorted(res, reverse=True)) - + class CreationOperator(VertexOperator): - """ - + r""" + EXAMPLES:: sage: from sage.algebras.vertex_operators import * @@ -244,56 +285,92 @@ class CreationOperator(VertexOperator): s[]*w sage: Cre.act(1, w) s[]*w^2 - sage: Cre.act(0, w^-1) - s[1] + sage: Cre.act(0, w^-1+ w^-2) + s[2]*w^-1 + s[1] sage: t = Cre.act(1, Cre.act(-1, w^-2)); t s[2, 1] sage: Cre.act(3, t) s[3, 2, 1]*w + sage: t + Cre.act(-1, Cre.act(1, w^-2)) + 0 """ def __init__(self, fockspace): self.weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) self.x, self.dx = self.weyl_algebra.gens() super().__init__(lambda n: self.x[n], lambda n: -self.dx[n]/n, dcharge=1, fockspace=fockspace) - def _get_operator(self, i, x, c): - op = 0 - for j in range(self.cutoff(x)+1): + def _get_operator(self, i, cutoff, c): + r""" + Compute the coefficient of `z^{i - c}` in the vertex operator + + .. MATH:: + + \exp \left( \sum_{j \geq 1} x_j z^j \right) \exp\left(\sum_{j \geq 1} (-dx_j/j)z^{-j}\right)wz^{Q} + + Thought the coefficient is an infinite sum of weyl algebra elements, we + only compute the first ``cutoff`` many terms. + + INPUT: + + - ``i`` -- (integer) + - ``cutoff`` -- (integer) how far to expand the product + - ``c`` -- (integer) + """ + op = 0 + for j in range(cutoff+1): if i + j - c < 0: continue op += self.pos[i + j - c]*self.neg[j] return op -# TODO: Fix mysterious off by one error causing tests to fail. class AnnihilationOperator(VertexOperator): - """ - + r""" + EXAMPLES:: sage: from sage.algebras.vertex_operators import * sage: B. = LaurentPolynomialRing(SymmetricFunctions(QQ).s()) sage: Ann = AnnihilationOperator(B) - sage: Ann.act(0, B.one()) # known bug + sage: Ann.act(0, B.one()) 0 - sage: Ann.act(-1, B.one()) # known bug + sage: Ann.act(-1, B.one()) s[]*w^-1 - sage: Ann.act(-2, w^-1) # known bug + sage: Ann.act(-2, w^-1) s[]*w^-2 - sage: Ann.act(-1, w) # known bug - s[1] - sage: Ann.act(-2, Ann.act(0, w^2)) # known bug - s[2, 1] + sage: Ann.act(-1, w) + -s[1] + sage: Ann.act(-2, Ann.act(0, w^2)) + -s[2, 1] + sage: Ann.act(-2, Ann.act(0, w^2)) + Ann.act(0, Ann.act(-2, w^2)) + 0 """ def __init__(self, fockspace): - self.weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) - self.x, self.dx = self.weyl_algebra.gens() - super().__init__(lambda n: -self.x[n], lambda n: self.dx[n]/n, dcharge=-1, fockspace=fockspace) + self.weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) + self.x, self.dx = self.weyl_algebra.gens() + super().__init__(lambda n: -self.x[n], lambda n: self.dx[n]/n, dcharge=-1, fockspace=fockspace) - def _get_operator(self, i, x, c): - op = 0 - for j in range(self.cutoff(x)+1): - if -i + j + c < 0: + def _get_operator(self, i, cutoff, c): + r""" + Compute the coefficient of `z^{-i + c - 1}` in the vertex operator + + .. MATH:: + + \exp \left( \sum_{j \geq 1} -x_j z^j \right) \exp\left(\sum_{j \geq 1} (dx_j/j)z^{-j}\right)z^{-Q}w^{-1} + + Thought the coefficient is an infinite sum of weyl algebra elements, we + only compute the first ``cutoff`` many terms. + + INPUT: + + - ``i`` -- (integer) + - ``cutoff`` -- (integer) how far to expand the product + - ``c`` -- (integer) + """ + op = 0 + for j in range(cutoff+1): + if j - i + c - 1 < 0: continue - op += self.pos[-i + j + c]*self.neg[j] - return op \ No newline at end of file + + op += self.pos[j - i + c - 1]*self.neg[j] + return op From 77f9e678324732f24bcb0277f71078157b2cdad7 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Sun, 3 Aug 2025 11:57:32 -0500 Subject: [PATCH 06/11] Add a class implementing products of vertex operators --- src/sage/algebras/vertex_operators.py | 92 +++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index 3a31550c727..ee6a4c69503 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -28,6 +28,7 @@ from sage.misc.misc_c import prod from sage.rings.infinity import PlusInfinity from sage.rings.integer_ring import ZZ +from sage.rings.lazy_series_ring import LazyLaurentSeriesRing from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ from sage.sets.non_negative_integers import NonNegativeIntegers @@ -62,7 +63,7 @@ class Current(Action): The action of the current operators `J_i` on the Fermionic Fock space. For `i \neq 0`, we can view `J_i` as moving a particle by `-i` in all possible ways. Equivalently, viewed on the bosonic side, the action of `J_i` can be calculated - using the Murnaghan-Nakayam rule. + using the Murnaghan-Nakayama rule. EXAMPLES:: @@ -191,9 +192,14 @@ def __getitem__(self, i): raise ValueError("Invalid input") -class VertexOperator(Action): + + + +class VertexOperator(): r""" - The action of a Vertex Operator on the Bosonic Fock space. + The action of a Vertex Operator on the Bosonic Fock space. Users should not + create instances of this class directly, but instead use one of the defined + subclasses. INPUT: @@ -205,24 +211,29 @@ class VertexOperator(Action): far to expand the vertex operator (default: ``lambda x: max(x.degree(), 1)``) - ``dcharge`` -- integer (default: ``1``) indicating how this vertex operator should change the charge of an element - - ``fockspace``-- the space that the vertex operators are acting on. + - ``fockspace``-- the space that the vertex operators are acting on """ def __init__(self, pos, neg, cutoff=lambda x: max(x.degree(), 1), dcharge=1, fockspace=None): + self.pos = HalfVertexOperator(pos) self.neg = HalfVertexOperator(neg) if fockspace is None: self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=('w')) else: # TODO: check input is correct type self.fockspace = fockspace + + # self.spectral = LazyLaurentSeriesRing(self.fockspace, names = ('z',)) self.cutoff = cutoff self.dcharge = dcharge - super().__init__(ZZ, self.fockspace) + # super().__init__(ZZ, self.fockspace) - def _act_(self, i, x): + # def act_on_fock_space_element(self, x): + # return self.spectral(lambda n: self.act_by_mode(n,x), valuation = -1) + def act_by_mode(self, i, x): r""" Action of the ``i``'th Fourier mode of ``self`` on element ``x`` of - ``self.fockspace`` + ``self.fockspace``. """ res = 0 # for each component of constant charge, compute the action @@ -231,6 +242,8 @@ def _act_(self, i, x): return res + act = act_by_mode + def _act_on_sym(self, i, x, c): r""" Action of the ``i``'th Fourier mode of ``self`` on a homoegeneous element @@ -238,8 +251,11 @@ def _act_on_sym(self, i, x, c): """ p = self.fockspace.base_ring().symmetric_function_ring().p() op = self._get_operator(i, self.cutoff(x), c) + + # op is a scalar if op in self.fockspace.base_ring().base_ring(): return op*x + res = 0 for (m, c) in op.monomial_coefficients().items(): par1 = self._d_to_par(m[0].dict()) @@ -269,7 +285,69 @@ def _d_to_par(self, m): for i in m: res += [i]*int(m[i]) return Partition(sorted(res, reverse=True)) + +class ProductOfVertexOperators(): + def __init__(self, vertex_ops): + self.vertex_ops = vertex_ops + self.fockspace = self.vertex_ops[0].fockspace + assert all(op.fockspace is self.fockspace for op in self.vertex_ops) + + def get_monomial_coefficient(self, mon, x): + """ + + EXAMPLES:: + sage: from sage.algebras.vertex_operators import * + sage: B = BosonicFockSpace() + sage: Cre = CreationOperator(B) + sage: P = ProductOfVertexOperators([Cre, Cre, Cre]) + sage: P.get_monomial_coefficient([3, 2, 1],B.one()) + s[1, 1, 1]*w^3 + sage: P.get_monomial_coefficient([3, 1, 2],B.one()) + -s[1, 1, 1]*w^3 + sage: Ann = AnnihilationOperator(B) + sage: P = ProductOfVertexOperators([Ann]*3) + sage: P.get_monomial_coefficient([-4, -3, -2],B.one()) + -s[3]*w^-3 + sage: P.get_monomial_coefficient([-3, -2, -4],B.one()) + -s[3]*w^-3 + """ + # BUG: This currently gives the wrong power for Annihilation operators + # This is coming from the fact that their series form is \sum \psi_i^* z^-i + # The code for getting their Fourier mode picks out the action of \psi_i, NOT the coefficient of z^i + # The former makes more sense when you just want the action of a single clifford element, but the latter makes + # more sense when you want to deal with series expansions + if len(mon) != len(self.vertex_ops): + raise ValueError + for i in range(len(mon) -1, -1, -1): + x = self.vertex_ops[i].act(mon[i], x) + if x == 0: + break + return self.fockspace(x) + + def vacuum_expectation(self, cutoff=4): + """ + EXAMPLES:: + + sage: from sage.algebras.vertex_operators import * + sage: B = BosonicFockSpace() + sage: Cre = CreationOperator(B) + sage: Ann = AnnihilationOperator(B) + sage: P = ProductOfVertexOperators([Ann, Cre]) + sage: P.vacuum_expectation() + {(0, 0): 1, (1, 1): 1, (2, 2): 1, (3, 3): 1, (4, 4): 1} + sage: P = ProductOfVertexOperators([Cre, Ann]) + sage: P.vacuum_expectation() + {(-4, -4): 1, (-3, -3): 1, (-2, -2): 1, (-1, -1): 1} + """ + from itertools import product + vac = self.vertex_ops[0].fockspace.one() + res = {} + for m in product(range(-cutoff, cutoff+1),repeat=len(self.vertex_ops)): + c = self.get_monomial_coefficient(m, vac).constant_coefficient().monomial_coefficients().get(Partition([]), 0) + if c != 0: + res[m] = c + return res class CreationOperator(VertexOperator): r""" From 81d602406285a1256c672964d8cda2af41aa7f38 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Mon, 4 Aug 2025 21:03:49 -0500 Subject: [PATCH 07/11] Add general matrix coefficients for product of vertex operators --- src/sage/algebras/vertex_operators.py | 75 +++++++++++++++++++++------ 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index ee6a4c69503..aed6e693d2a 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -1,7 +1,13 @@ r""" +Vertex Operators + Currently just a sandbox for experimenting with vertex operator code before a more permanent implementation +AUTHORS: + +- Joseph McDonough (2025-08-04): Initial version + TESTS:: sage: from sage.algebras.vertex_operators import * sage: R = SymmetricFunctions(QQ); p = R.p(); s = R.s() @@ -192,9 +198,6 @@ def __getitem__(self, i): raise ValueError("Invalid input") - - - class VertexOperator(): r""" The action of a Vertex Operator on the Bosonic Fock space. Users should not @@ -293,8 +296,17 @@ def __init__(self, vertex_ops): assert all(op.fockspace is self.fockspace for op in self.vertex_ops) def get_monomial_coefficient(self, mon, x): - """ - + r""" + Compute the action of ``self`` on ``x``. + + Let `X^i_j` denote the `j`'th Fourier mode of the `i`'th vertex operator of ``self``. + This method computes `X^1_{mon_1}\cdots X^m_{mon_m}\left|x\right\rangle`. + + INPUT: + + - ``mon`` -- a list of integers of length equal to the number of vertex operators + - ``x`` -- an element of ``self.fockspace`` to be acted on. + EXAMPLES:: sage: from sage.algebras.vertex_operators import * @@ -309,8 +321,8 @@ def get_monomial_coefficient(self, mon, x): sage: P = ProductOfVertexOperators([Ann]*3) sage: P.get_monomial_coefficient([-4, -3, -2],B.one()) -s[3]*w^-3 - sage: P.get_monomial_coefficient([-3, -2, -4],B.one()) - -s[3]*w^-3 + sage: P.get_monomial_coefficient([-4, -2, -3],B.one()) + s[3]*w^-3 """ # BUG: This currently gives the wrong power for Annihilation operators # This is coming from the fact that their series form is \sum \psi_i^* z^-i @@ -325,30 +337,55 @@ def get_monomial_coefficient(self, mon, x): break return self.fockspace(x) - def vacuum_expectation(self, cutoff=4): + def matrix_coefficient(self, bra, ket, cutoff=4): """ + Approximate the matrix coefficient , where X is the vertex operator + represented by ``self`` + + INPUT: + + - ``bra``, ``ket`` -- ordered pair (`\lambda`, c) consisting of an integer partition and an integer, indexing a basis element of the Fock space. + - ``cutoff`` -- Nonnegative integer indicating how far to expand the vertex operator. + EXAMPLES:: sage: from sage.algebras.vertex_operators import * sage: B = BosonicFockSpace() sage: Cre = CreationOperator(B) sage: Ann = AnnihilationOperator(B) - sage: P = ProductOfVertexOperators([Ann, Cre]) - sage: P.vacuum_expectation() - {(0, 0): 1, (1, 1): 1, (2, 2): 1, (3, 3): 1, (4, 4): 1} - sage: P = ProductOfVertexOperators([Cre, Ann]) - sage: P.vacuum_expectation() - {(-4, -4): 1, (-3, -3): 1, (-2, -2): 1, (-1, -1): 1} + sage: P = ProductOfVertexOperators([Cre, Cre]) + sage: P.matrix_coefficient(([],3), ([],1)) # example of eq 2.21 in [AZ13] + {(1, 2): -1, (2, 1): 1} """ from itertools import product - vac = self.vertex_ops[0].fockspace.one() + w = self.fockspace.gen() + R = self.fockspace.base_ring() + f = (w**ket[1])*R(ket[0]) res = {} for m in product(range(-cutoff, cutoff+1),repeat=len(self.vertex_ops)): - c = self.get_monomial_coefficient(m, vac).constant_coefficient().monomial_coefficients().get(Partition([]), 0) + c = self.get_monomial_coefficient(m, f).monomial_coefficients().get(bra[1],self.fockspace.zero()).monomial_coefficients().get(Partition(bra[0]),self.fockspace.zero()) if c != 0: res[m] = c return res + def vacuum_expectation(self, cutoff=4): + """ + EXAMPLES:: + + sage: from sage.algebras.vertex_operators import * + sage: B = BosonicFockSpace() + sage: Cre = CreationOperator(B) + sage: Ann = AnnihilationOperator(B) + sage: P = ProductOfVertexOperators([Ann, Cre]) + sage: P.vacuum_expectation() + {(0, 0): 1, (1, 1): 1, (2, 2): 1, (3, 3): 1, (4, 4): 1} + sage: P = ProductOfVertexOperators([Cre, Ann]) + sage: P.vacuum_expectation() + {(-4, -4): 1, (-3, -3): 1, (-2, -2): 1, (-1, -1): 1} + """ + return self.matrix_coefficient(([],0),([],0), cutoff) + + class CreationOperator(VertexOperator): r""" @@ -400,11 +437,13 @@ def _get_operator(self, i, cutoff, c): continue op += self.pos[i + j - c]*self.neg[j] return op - + def _repr_(self): + return f"The creation vertex operator acting on {self.fockspace}" class AnnihilationOperator(VertexOperator): r""" + EXAMPLES:: sage: from sage.algebras.vertex_operators import * @@ -452,3 +491,5 @@ def _get_operator(self, i, cutoff, c): op += self.pos[j - i + c - 1]*self.neg[j] return op + def _repr_(self): + return f"The annihilation vertex operator acting on {self.fockspace}" \ No newline at end of file From c38d5dbe9b3bdac6c4966ad3c67a76c78ad70ff7 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Tue, 5 Aug 2025 10:28:44 -0500 Subject: [PATCH 08/11] lint --- src/sage/algebras/vertex_operators.py | 33 +++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index aed6e693d2a..742a206e3de 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -218,14 +218,14 @@ class VertexOperator(): """ def __init__(self, pos, neg, cutoff=lambda x: max(x.degree(), 1), dcharge=1, fockspace=None): - + self.pos = HalfVertexOperator(pos) self.neg = HalfVertexOperator(neg) if fockspace is None: self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=('w')) else: # TODO: check input is correct type self.fockspace = fockspace - + # self.spectral = LazyLaurentSeriesRing(self.fockspace, names = ('z',)) self.cutoff = cutoff self.dcharge = dcharge @@ -254,11 +254,11 @@ def _act_on_sym(self, i, x, c): """ p = self.fockspace.base_ring().symmetric_function_ring().p() op = self._get_operator(i, self.cutoff(x), c) - + # op is a scalar if op in self.fockspace.base_ring().base_ring(): return op*x - + res = 0 for (m, c) in op.monomial_coefficients().items(): par1 = self._d_to_par(m[0].dict()) @@ -288,7 +288,8 @@ def _d_to_par(self, m): for i in m: res += [i]*int(m[i]) return Partition(sorted(res, reverse=True)) - + + class ProductOfVertexOperators(): def __init__(self, vertex_ops): self.vertex_ops = vertex_ops @@ -327,18 +328,18 @@ def get_monomial_coefficient(self, mon, x): # BUG: This currently gives the wrong power for Annihilation operators # This is coming from the fact that their series form is \sum \psi_i^* z^-i # The code for getting their Fourier mode picks out the action of \psi_i, NOT the coefficient of z^i - # The former makes more sense when you just want the action of a single clifford element, but the latter makes + # The former makes more sense when you just want the action of a single clifford element, but the latter makes # more sense when you want to deal with series expansions if len(mon) != len(self.vertex_ops): raise ValueError - for i in range(len(mon) -1, -1, -1): + for i in range(len(mon) - 1, -1, -1): x = self.vertex_ops[i].act(mon[i], x) if x == 0: break return self.fockspace(x) - + def matrix_coefficient(self, bra, ket, cutoff=4): - """ + r""" Approximate the matrix coefficient , where X is the vertex operator represented by ``self`` @@ -362,8 +363,9 @@ def matrix_coefficient(self, bra, ket, cutoff=4): R = self.fockspace.base_ring() f = (w**ket[1])*R(ket[0]) res = {} - for m in product(range(-cutoff, cutoff+1),repeat=len(self.vertex_ops)): - c = self.get_monomial_coefficient(m, f).monomial_coefficients().get(bra[1],self.fockspace.zero()).monomial_coefficients().get(Partition(bra[0]),self.fockspace.zero()) + for m in product(range(-cutoff, cutoff + 1), repeat=len(self.vertex_ops)): + c = self.get_monomial_coefficient(m, f).monomial_coefficients().get( + bra[1], self.fockspace.zero()).monomial_coefficients().get(Partition(bra[0]), self.fockspace.zero()) if c != 0: res[m] = c return res @@ -377,13 +379,13 @@ def vacuum_expectation(self, cutoff=4): sage: Cre = CreationOperator(B) sage: Ann = AnnihilationOperator(B) sage: P = ProductOfVertexOperators([Ann, Cre]) - sage: P.vacuum_expectation() + sage: P.vacuum_expectation() {(0, 0): 1, (1, 1): 1, (2, 2): 1, (3, 3): 1, (4, 4): 1} sage: P = ProductOfVertexOperators([Cre, Ann]) sage: P.vacuum_expectation() {(-4, -4): 1, (-3, -3): 1, (-2, -2): 1, (-1, -1): 1} """ - return self.matrix_coefficient(([],0),([],0), cutoff) + return self.matrix_coefficient(([], 0), ([], 0), cutoff) class CreationOperator(VertexOperator): @@ -437,9 +439,11 @@ def _get_operator(self, i, cutoff, c): continue op += self.pos[i + j - c]*self.neg[j] return op + def _repr_(self): return f"The creation vertex operator acting on {self.fockspace}" + class AnnihilationOperator(VertexOperator): r""" @@ -491,5 +495,6 @@ def _get_operator(self, i, cutoff, c): op += self.pos[j - i + c - 1]*self.neg[j] return op + def _repr_(self): - return f"The annihilation vertex operator acting on {self.fockspace}" \ No newline at end of file + return f"The annihilation vertex operator acting on {self.fockspace}" From 29a1feaea8cb90625ed15a83d1f6034e8c3eb1a2 Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Mon, 11 Aug 2025 18:40:56 -0500 Subject: [PATCH 09/11] fix annihilation operator indexing bug --- src/sage/algebras/vertex_operators.py | 74 ++++++++++++++++++--------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index 742a206e3de..8f1627e7114 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -1,9 +1,6 @@ r""" Vertex Operators -Currently just a sandbox for experimenting with vertex operator code -before a more permanent implementation - AUTHORS: - Joseph McDonough (2025-08-04): Initial version @@ -38,7 +35,7 @@ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.rational_field import QQ from sage.sets.non_negative_integers import NonNegativeIntegers - +from sage.structure.sage_object import SageObject class FermionicFockSpace(CombinatorialFreeModule): r""" @@ -51,6 +48,7 @@ class FermionicFockSpace(CombinatorialFreeModule): sage: F = FermionicFockSpace(QQ) sage: F.an_element() |[], 0> + 4*|[], 1> + 2*|[1], 0> + sage: TestSuite(F).run() """ def __init__(self, R): index_set = cartesian_product((Partitions(), ZZ)) @@ -63,6 +61,22 @@ def _repr_term(self, m): def BosonicFockSpace(names=('w',)): return LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=names) +def BFMap(): + r""" + + TESTS:: + + sage: from sage.algebras.vertex_operators import * + sage: F = FermionicFockSpace(QQ) + sage: phi = BFMap() + sage: phi(F(([2,1],-1)) + F(([],0))) + s[2, 1]*w^-1 + s[] + """ + F = FermionicFockSpace(QQ) + B = BosonicFockSpace() + w = B.gen() + s = B.base_ring() + return F.module_morphism(on_basis=lambda b: w**(b[1])*s(b[0]), codomain = B) class Current(Action): r""" @@ -198,7 +212,7 @@ def __getitem__(self, i): raise ValueError("Invalid input") -class VertexOperator(): +class VertexOperator(SageObject): r""" The action of a Vertex Operator on the Bosonic Fock space. Users should not create instances of this class directly, but instead use one of the defined @@ -229,10 +243,10 @@ def __init__(self, pos, neg, cutoff=lambda x: max(x.degree(), 1), dcharge=1, foc # self.spectral = LazyLaurentSeriesRing(self.fockspace, names = ('z',)) self.cutoff = cutoff self.dcharge = dcharge - # super().__init__(ZZ, self.fockspace) # def act_on_fock_space_element(self, x): # return self.spectral(lambda n: self.act_by_mode(n,x), valuation = -1) + def act_by_mode(self, i, x): r""" Action of the ``i``'th Fourier mode of ``self`` on element ``x`` of @@ -290,7 +304,7 @@ def _d_to_par(self, m): return Partition(sorted(res, reverse=True)) -class ProductOfVertexOperators(): +class ProductOfVertexOperators(SageObject): def __init__(self, vertex_ops): self.vertex_ops = vertex_ops self.fockspace = self.vertex_ops[0].fockspace @@ -320,16 +334,11 @@ def get_monomial_coefficient(self, mon, x): -s[1, 1, 1]*w^3 sage: Ann = AnnihilationOperator(B) sage: P = ProductOfVertexOperators([Ann]*3) - sage: P.get_monomial_coefficient([-4, -3, -2],B.one()) + sage: P.get_monomial_coefficient([4, 3, 2],B.one()) -s[3]*w^-3 - sage: P.get_monomial_coefficient([-4, -2, -3],B.one()) + sage: P.get_monomial_coefficient([4, 2, 3],B.one()) s[3]*w^-3 """ - # BUG: This currently gives the wrong power for Annihilation operators - # This is coming from the fact that their series form is \sum \psi_i^* z^-i - # The code for getting their Fourier mode picks out the action of \psi_i, NOT the coefficient of z^i - # The former makes more sense when you just want the action of a single clifford element, but the latter makes - # more sense when you want to deal with series expansions if len(mon) != len(self.vertex_ops): raise ValueError for i in range(len(mon) - 1, -1, -1): @@ -380,17 +389,18 @@ def vacuum_expectation(self, cutoff=4): sage: Ann = AnnihilationOperator(B) sage: P = ProductOfVertexOperators([Ann, Cre]) sage: P.vacuum_expectation() - {(0, 0): 1, (1, 1): 1, (2, 2): 1, (3, 3): 1, (4, 4): 1} + {(-4, 4): 1, (-3, 3): 1, (-2, 2): 1, (-1, 1): 1, (0, 0): 1} sage: P = ProductOfVertexOperators([Cre, Ann]) sage: P.vacuum_expectation() - {(-4, -4): 1, (-3, -3): 1, (-2, -2): 1, (-1, -1): 1} + {(-4, 4): 1, (-3, 3): 1, (-2, 2): 1, (-1, 1): 1} """ return self.matrix_coefficient(([], 0), ([], 0), cutoff) class CreationOperator(VertexOperator): r""" - + The generating series for (bosonic) creation operators. + EXAMPLES:: sage: from sage.algebras.vertex_operators import * @@ -440,13 +450,21 @@ def _get_operator(self, i, cutoff, c): op += self.pos[i + j - c]*self.neg[j] return op + def act_by_clifford_gen(self, i, x): + return self.act_by_mode(i, x) + def _repr_(self): return f"The creation vertex operator acting on {self.fockspace}" class AnnihilationOperator(VertexOperator): r""" + The generating series for (bosonic) annihilation operators. + + .. WARNING:: + Following the literature, the operator corresponding to the coefficient of + `z^i` is `\psi_{-i}`, **not** `\psi_i`. EXAMPLES:: @@ -455,15 +473,15 @@ class AnnihilationOperator(VertexOperator): sage: Ann = AnnihilationOperator(B) sage: Ann.act(0, B.one()) 0 - sage: Ann.act(-1, B.one()) + sage: Ann.act(1, B.one()) s[]*w^-1 - sage: Ann.act(-2, w^-1) + sage: Ann.act(2, w^-1) s[]*w^-2 - sage: Ann.act(-1, w) + sage: Ann.act(1, w) -s[1] - sage: Ann.act(-2, Ann.act(0, w^2)) + sage: Ann.act(2, Ann.act(0, w^2)) -s[2, 1] - sage: Ann.act(-2, Ann.act(0, w^2)) + Ann.act(0, Ann.act(-2, w^2)) + sage: Ann.act(2, Ann.act(0, w^2)) + Ann.act(0, Ann.act(2, w^2)) 0 """ def __init__(self, fockspace): @@ -473,7 +491,7 @@ def __init__(self, fockspace): def _get_operator(self, i, cutoff, c): r""" - Compute the coefficient of `z^{-i + c - 1}` in the vertex operator + Compute the coefficient of `z^{i + c - 1}` in the vertex operator .. MATH:: @@ -490,11 +508,17 @@ def _get_operator(self, i, cutoff, c): """ op = 0 for j in range(cutoff+1): - if j - i + c - 1 < 0: + if j + i + c - 1 < 0: continue - op += self.pos[j - i + c - 1]*self.neg[j] + op += self.pos[j + i + c - 1]*self.neg[j] return op + def act_by_clifford_gen(self,i, x): + """ + Action of the coefficient of z^{-i} on Fock space element ``x``. + """ + return self.act_by_mode(-i, x) + def _repr_(self): return f"The annihilation vertex operator acting on {self.fockspace}" From e338b04985a7b0add5ee82c9840d9a739c8ce9ba Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Mon, 11 Aug 2025 19:24:07 -0500 Subject: [PATCH 10/11] speed up matrix coefficient computation and add some more docs --- src/sage/algebras/vertex_operators.py | 52 +++++++++++++++++++-------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index 8f1627e7114..8f48a0b806f 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -27,7 +27,7 @@ from sage.combinat.sf.sf import SymmetricFunctions from sage.data_structures.stream import Stream_function, Stream_cauchy_compose from sage.functions.other import factorial -from sage.misc.cachefunc import cached_function +from sage.misc.cachefunc import cached_function, cached_method from sage.misc.misc_c import prod from sage.rings.infinity import PlusInfinity from sage.rings.integer_ring import ZZ @@ -37,6 +37,7 @@ from sage.sets.non_negative_integers import NonNegativeIntegers from sage.structure.sage_object import SageObject + class FermionicFockSpace(CombinatorialFreeModule): r""" A model of the Fermionic Fock space `\mathcal{H}_F`. As a vector space, `\mathcal{H}_F` @@ -61,9 +62,14 @@ def _repr_term(self, m): def BosonicFockSpace(names=('w',)): return LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=names) + def BFMap(): r""" - + Returns a map that compute the image of an element of the Fermionic Fock space + under the Boson-Fermion correspondence. The basis element `|\lambda, n \rangle` + gets mapped to `w^ns_\lambda`, where `w` is the charge variable and `s_\lambda` + is the Schur function corresponding to `\lambda`. + TESTS:: sage: from sage.algebras.vertex_operators import * @@ -76,7 +82,8 @@ def BFMap(): B = BosonicFockSpace() w = B.gen() s = B.base_ring() - return F.module_morphism(on_basis=lambda b: w**(b[1])*s(b[0]), codomain = B) + return F.module_morphism(on_basis=lambda b: w**(b[1])*s(b[0]), codomain=B) + class Current(Action): r""" @@ -247,6 +254,7 @@ def __init__(self, pos, neg, cutoff=lambda x: max(x.degree(), 1), dcharge=1, foc # def act_on_fock_space_element(self, x): # return self.spectral(lambda n: self.act_by_mode(n,x), valuation = -1) + @cached_method def act_by_mode(self, i, x): r""" Action of the ``i``'th Fourier mode of ``self`` on element ``x`` of @@ -349,8 +357,7 @@ def get_monomial_coefficient(self, mon, x): def matrix_coefficient(self, bra, ket, cutoff=4): r""" - Approximate the matrix coefficient , where X is the vertex operator - represented by ``self`` + Approximate the matrix coefficient `\langle` ``bra`` `|` ``self`` `|` ``ket`` `\rangle`. INPUT: @@ -380,7 +387,9 @@ def matrix_coefficient(self, bra, ket, cutoff=4): return res def vacuum_expectation(self, cutoff=4): - """ + r""" + Computes the matrix coefficient `\langle \varnothing | X | \varnothing \rangle` + EXAMPLES:: sage: from sage.algebras.vertex_operators import * @@ -393,14 +402,23 @@ def vacuum_expectation(self, cutoff=4): sage: P = ProductOfVertexOperators([Cre, Ann]) sage: P.vacuum_expectation() {(-4, 4): 1, (-3, 3): 1, (-2, 2): 1, (-1, 1): 1} + + This verifies that, letting `\psi(z), \psi^*(w)` denote the fermionic fields, + the matrix coefficient of their product is given by `\langle \varnothing | \psi^*(w)\psi^(z)|\varnothing\rangle = \frac{w}{w-z}` """ return self.matrix_coefficient(([], 0), ([], 0), cutoff) class CreationOperator(VertexOperator): r""" - The generating series for (bosonic) creation operators. - + The image under the boson-fermion correspond of the fermionic field `\psi^*(z)`. + + Explicitly, the action on the bosonic fock space is given by the series + + .. MATH:: + + \exp \left( \sum_{j \geq 1} x_j z^j \right) \exp\left(\sum_{j \geq 1} (-dx_j/j)z^{-j}\right)wz^{Q} + EXAMPLES:: sage: from sage.algebras.vertex_operators import * @@ -459,11 +477,17 @@ def _repr_(self): class AnnihilationOperator(VertexOperator): r""" - The generating series for (bosonic) annihilation operators. - + The image under the boson-fermion correspond of the fermionic field `\psi^*(z)`. + + Explicitly, the action on the bosonic fock space is given by the series + + .. MATH:: + + \exp \left( \sum_{j \geq 1} -x_j z^j \right) \exp\left(\sum_{j \geq 1} (dx_j/j)z^{-j}\right)z^{-Q}w^{-1} + .. WARNING:: - Following the literature, the operator corresponding to the coefficient of + Following the literature, the operator corresponding to the coefficient of `z^i` is `\psi_{-i}`, **not** `\psi_i`. EXAMPLES:: @@ -514,11 +538,11 @@ def _get_operator(self, i, cutoff, c): op += self.pos[j + i + c - 1]*self.neg[j] return op - def act_by_clifford_gen(self,i, x): + def act_by_clifford_gen(self, i, x): """ - Action of the coefficient of z^{-i} on Fock space element ``x``. + Action of the coefficient of `z^{-i}` on Fock space element ``x``. """ return self.act_by_mode(-i, x) - + def _repr_(self): return f"The annihilation vertex operator acting on {self.fockspace}" From c383e649ce0c15b30a409e20d3612b5399ae80ad Mon Sep 17 00:00:00 2001 From: Joe McDonough Date: Mon, 18 Aug 2025 09:40:05 -0500 Subject: [PATCH 11/11] cache vertex operator action on basis of fock space --- src/sage/algebras/vertex_operators.py | 129 +++++++++++++++++++------- 1 file changed, 94 insertions(+), 35 deletions(-) diff --git a/src/sage/algebras/vertex_operators.py b/src/sage/algebras/vertex_operators.py index 8f48a0b806f..a9003a0956d 100644 --- a/src/sage/algebras/vertex_operators.py +++ b/src/sage/algebras/vertex_operators.py @@ -1,4 +1,4 @@ -r""" +r""" sage.doctest needs sage.algebras.vertex_operators Vertex Operators AUTHORS: @@ -6,6 +6,7 @@ - Joseph McDonough (2025-08-04): Initial version TESTS:: + sage: from sage.algebras.vertex_operators import * sage: R = SymmetricFunctions(QQ); p = R.p(); s = R.s() sage: P = p.completion() @@ -27,6 +28,7 @@ from sage.combinat.sf.sf import SymmetricFunctions from sage.data_structures.stream import Stream_function, Stream_cauchy_compose from sage.functions.other import factorial +from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_function, cached_method from sage.misc.misc_c import prod from sage.rings.infinity import PlusInfinity @@ -85,7 +87,7 @@ def BFMap(): return F.module_morphism(on_basis=lambda b: w**(b[1])*s(b[0]), codomain=B) -class Current(Action): +class Current(): r""" The action of the current operators `J_i` on the Fermionic Fock space. For `i \neq 0`, we can view `J_i` as moving a particle by `-i` in all possible ways. @@ -111,9 +113,8 @@ def __init__(self): self._s = self._R.s() self._p = self._R.p() self.fockspace = FermionicFockSpace(self._s) - super().__init__(ZZ, self.fockspace) - def _act_(self, g, x): + def act(self, g, x): res = self.fockspace.zero() if g > 0: # \partial p_k for (m, c) in x.monomial_coefficients().items(): @@ -133,7 +134,7 @@ def _act_(self, g, x): def act_by_partition(self, lam, sign, x): for i in lam: # TODO: Make this more efficient by acting by the whole partition at once - x = self._act_(i*sign, x) + x = self.act(i*sign, x) return x @@ -219,7 +220,16 @@ def __getitem__(self, i): raise ValueError("Invalid input") -class VertexOperator(SageObject): +class AbstractVertexOperator(SageObject): + def __init__(self, fockspace): + self._fockspace = fockspace #TODO: Check input validity + pass + + @abstract_method + def matrix_coefficient(self, bra, ket): pass + + +class VertexOperator(AbstractVertexOperator): r""" The action of a Vertex Operator on the Bosonic Fock space. Users should not create instances of this class directly, but instead use one of the defined @@ -243,14 +253,12 @@ def __init__(self, pos, neg, cutoff=lambda x: max(x.degree(), 1), dcharge=1, foc self.pos = HalfVertexOperator(pos) self.neg = HalfVertexOperator(neg) if fockspace is None: - self.fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=('w')) - else: # TODO: check input is correct type - self.fockspace = fockspace + fockspace = LaurentPolynomialRing(SymmetricFunctions(QQ).s(), names=('w')) # self.spectral = LazyLaurentSeriesRing(self.fockspace, names = ('z',)) self.cutoff = cutoff self.dcharge = dcharge - + super().__init__(fockspace) # def act_on_fock_space_element(self, x): # return self.spectral(lambda n: self.act_by_mode(n,x), valuation = -1) @@ -260,10 +268,10 @@ def act_by_mode(self, i, x): Action of the ``i``'th Fourier mode of ``self`` on element ``x`` of ``self.fockspace``. """ - res = 0 + res = self._fockspace.zero() # for each component of constant charge, compute the action for (charge, fn) in x.monomial_coefficients().items(): - res += self.fockspace.gen()**(charge+self.dcharge)*self._act_on_sym(i, fn, charge) + res += self._fockspace.gen()**(charge+self.dcharge)*self._act_on_sym(i, fn, charge) return res @@ -274,23 +282,36 @@ def _act_on_sym(self, i, x, c): Action of the ``i``'th Fourier mode of ``self`` on a homoegeneous element of ``self.fockspace``. """ - p = self.fockspace.base_ring().symmetric_function_ring().p() + R = self._fockspace.base_ring() op = self._get_operator(i, self.cutoff(x), c) # op is a scalar - if op in self.fockspace.base_ring().base_ring(): + if op in R.base_ring(): return op*x - res = 0 - for (m, c) in op.monomial_coefficients().items(): + res = R.zero() + for (m, c) in x.monomial_coefficients().items(): + res += c*self._act_on_basis(op, m) + return res + + @cached_method + def _act_on_basis(self, op, x): + """ + Action of a differential operator ``op`` on a basis element of the fock space. + """ + R = self._fockspace.base_ring() + p = R.symmetric_function_ring().p() + + res = R.zero() + for (m,c) in op.monomial_coefficients().items(): par1 = self._d_to_par(m[0].dict()) par2 = self._d_to_par(m[1].dict()) - for (m2, c2) in x.monomial_coefficients().items(): - res += c*c2*(self.fockspace.base_ring()(m2).skew_by(p(par2)) * p(par1)/(prod(par1))) + res += c*R(x).skew_by(p(par2)) * p(par1) / prod(par1) return res - def _get_operator(self, i, x, c): - raise NotImplementedError("Use a subclass of VertexOperator") + + @abstract_method + def _get_operator(self, i, x, c): pass def _d_to_par(self, m): """ @@ -312,11 +333,36 @@ def _d_to_par(self, m): return Partition(sorted(res, reverse=True)) -class ProductOfVertexOperators(SageObject): +class ProductOfVertexOperators(AbstractVertexOperator): def __init__(self, vertex_ops): self.vertex_ops = vertex_ops - self.fockspace = self.vertex_ops[0].fockspace - assert all(op.fockspace is self.fockspace for op in self.vertex_ops) + + assert all(op._fockspace is vertex_ops[0]._fockspace for op in self.vertex_ops) + self._num_ops = len(vertex_ops) + + """ + TODO: Figure out a way to define these properly. The problem is that you sort + of need to know the intermediate valuations before you compute what they need to be. + """ + + self._spectral = [0]*self._num_ops + self._spectral[0] = LazyLaurentSeriesRing(vertex_ops[0]._fockspace, names = ('z1')) + for i in range(1, self._num_ops): + self._spectral[i] = LazyLaurentSeriesRing(self._spectral[i-1], names=('z'+str(i+1))) + # self._spectral[-1] = LazyLaurentSeriesRing(vertex_ops[-1]._fockspace, names = ('z' + str(len(vertex_ops)))) + # for i in range(len(vertex_ops)-2, -1, -1): + # self._spectral[i] = LazyLaurentSeriesRing(self._spectral[i+1], names=('z'+str(i+1))) + super().__init__(self.vertex_ops[0]._fockspace) + + def full_action(self, f, valuation): + def action_helper(self, i, f): + if i == self._num_ops - 1: + return self.vertex_ops[i].full_action(f) + + return self._spectral[i] + pass + + # valuation = lambda x: -max(y.degree() for (_, y) in x.monomial_coefficients().items()) def get_monomial_coefficient(self, mon, x): r""" @@ -353,7 +399,7 @@ def get_monomial_coefficient(self, mon, x): x = self.vertex_ops[i].act(mon[i], x) if x == 0: break - return self.fockspace(x) + return self._fockspace(x) def matrix_coefficient(self, bra, ket, cutoff=4): r""" @@ -375,13 +421,13 @@ def matrix_coefficient(self, bra, ket, cutoff=4): {(1, 2): -1, (2, 1): 1} """ from itertools import product - w = self.fockspace.gen() - R = self.fockspace.base_ring() + w = self._fockspace.gen() + R = self._fockspace.base_ring() f = (w**ket[1])*R(ket[0]) res = {} for m in product(range(-cutoff, cutoff + 1), repeat=len(self.vertex_ops)): c = self.get_monomial_coefficient(m, f).monomial_coefficients().get( - bra[1], self.fockspace.zero()).monomial_coefficients().get(Partition(bra[0]), self.fockspace.zero()) + bra[1], self._fockspace.zero()).monomial_coefficients().get(Partition(bra[0]), self._fockspace.zero()) if c != 0: res[m] = c return res @@ -440,9 +486,10 @@ class CreationOperator(VertexOperator): 0 """ def __init__(self, fockspace): - self.weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) - self.x, self.dx = self.weyl_algebra.gens() - super().__init__(lambda n: self.x[n], lambda n: -self.dx[n]/n, dcharge=1, fockspace=fockspace) + self._weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) + self._x, self._dx = self._weyl_algebra.gens() + self._spectral = LazyLaurentSeriesRing(fockspace, names = ('z',)) + super().__init__(lambda n: self._x[n], lambda n: -self._dx[n]/n, dcharge=1, fockspace=fockspace) def _get_operator(self, i, cutoff, c): r""" @@ -468,11 +515,23 @@ def _get_operator(self, i, cutoff, c): op += self.pos[i + j - c]*self.neg[j] return op + def full_action(self, x): + + return self._spectral(lambda i: self.act_by_mode(i, x), valuation=-max(y.degree() for (_, y) in x.monomial_coefficients().items())) + + def matrix_coefficient(self, bra, ket): + w = self._fockspace.gen() + R = self._fockspace.base_ring() + f = (w**(ket[1]))*R(ket[0]) + zero = self._fockspace.zero() + return self._spectral(lambda i: self.act_by_mode(i, f).monomial_coefficients().get( + bra[1], zero).monomial_coefficients().get(Partition(bra[0]), zero), valuation = -Partition(ket[0]).size()-1) + def act_by_clifford_gen(self, i, x): return self.act_by_mode(i, x) def _repr_(self): - return f"The creation vertex operator acting on {self.fockspace}" + return f"The creation vertex operator acting on {self._fockspace}" class AnnihilationOperator(VertexOperator): @@ -509,9 +568,9 @@ class AnnihilationOperator(VertexOperator): 0 """ def __init__(self, fockspace): - self.weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) - self.x, self.dx = self.weyl_algebra.gens() - super().__init__(lambda n: -self.x[n], lambda n: self.dx[n]/n, dcharge=-1, fockspace=fockspace) + self._weyl_algebra = DifferentialWeylAlgebra(QQ, names=('x'), n=PlusInfinity()) + self._x, self._dx = self._weyl_algebra.gens() + super().__init__(lambda n: -self._x[n], lambda n: self._dx[n]/n, dcharge=-1, fockspace=fockspace) def _get_operator(self, i, cutoff, c): r""" @@ -545,4 +604,4 @@ def act_by_clifford_gen(self, i, x): return self.act_by_mode(-i, x) def _repr_(self): - return f"The annihilation vertex operator acting on {self.fockspace}" + return f"The annihilation vertex operator acting on {self._fockspace}"