r"""
This module contains algorithms related to wall-crossing.
We define the class :class:`WCS` associated to a lattice with a skew-symmetric
form (or to a quiver). This class contains the quantum affine plane where all
invariants live and where wall-crossing takes place. It also contains
algorithms to jump between various types of invariants:
1. Stacky, Rational, Integer invariants associated to a stability parameter.
2. Total invariant (stacky invariant associated to the trivial stability parameter).
3. Stacky, Rational, Integer invariants associated to the attractor stability.
4. Invariant counting stable representations of a quiver for a given stability parameter.
EXAMPLES::
sage: from msinvar import *
sage: Q=KroneckerQuiver(2); Q
Kronecker quiver: Quiver with 2 vertices and 2 arrows
sage: Q.prec([2,2]) #fix precision vector
sage: Q.total().dict()
{(0, 0): 1,
(0, 1): y/(-y^2 + 1),
(0, 2): y^2/(y^6 - y^4 - y^2 + 1),
(1, 0): y/(-y^2 + 1),
(1, 1): y^4/(y^4 - 2*y^2 + 1),
(1, 2): y^7/(-y^8 + 2*y^6 - 2*y^2 + 1),
(2, 0): y^2/(y^6 - y^4 - y^2 + 1),
(2, 1): y^7/(-y^8 + 2*y^6 - 2*y^2 + 1),
(2, 2): y^12/(y^12 - 2*y^10 - y^8 + 4*y^6 - y^4 - 2*y^2 + 1)}
::
sage: I=Q.stacky([1,0]); I.dict()
{(0, 0): 1,
(0, 1): y/(-y^2 + 1),
(0, 2): y^2/(y^6 - y^4 - y^2 + 1),
(1, 0): y/(-y^2 + 1),
(1, 1): (y^2 + 1)/(y^2 - 1),
(1, 2): y/(-y^2 + 1),
(2, 0): y^2/(y^6 - y^4 - y^2 + 1),
(2, 1): y/(-y^2 + 1),
(2, 2): (y^6 + y^4 + 2*y^2)/(y^6 - y^4 - y^2 + 1)}
sage: I1=Q.stk2int(I); I1.dict()
{(0, 1): 1, (1, 0): 1, (1, 1): (-y^2 - 1)/y, (1, 2): 1, (2, 1): 1}
::
sage: I=Q.stacky([0,1]); I.dict()
{(0, 0): 1,
(0, 1): y/(-y^2 + 1),
(0, 2): y^2/(y^6 - y^4 - y^2 + 1),
(1, 0): y/(-y^2 + 1),
(2, 0): y^2/(y^6 - y^4 - y^2 + 1)}
sage: I1=Q.stk2int(I); I1.dict()
{(0, 1): 1, (1, 0): 1}
::
sage: Q.intAtt().dict() #integer attractor invariants
{(0, 1): 1, (1, 0): 1}
::
sage: Q.stable([1,0], slope=1/2).dict() #invariants of stable moduli spaces
{(1, 1): y^2 + 1}
"""
# *****************************************************************************
# Copyright (C) 2021 Sergey Mozgovoy <mozhov@gmail.com>
#
# Distributed under the terms of the GNU General Public License (GPL)
# http://www.gnu.org/licenses/
# *****************************************************************************
from msinvar.rings import RF
from msinvar.tm_polynomials import TMPoly
from msinvar.utils import vec, cache
from msinvar.iterators import IntegerVectors_iterator
from msinvar.stability import Stability
from msinvar.invariants import Invariant, Psi_map, IPsi_map
from msinvar.invariants import log_transform, exp_transform, reineke_transform, joyce_transform
from msinvar.invariants import recursive_inversion
[docs]class WallCrossingStructure:
"""
Wall-crossing structure.
Contains the quantum affine algebra and methods to compute and transform
different types of invariants (stacky, rational, integer DT invariants
corresponding to different stability parameters).
INPUT:
- ``rank`` -- Rank of the lattice.
- ``sform`` -- Skew-symmetric form on the lattice (a function).
- ``prec`` -- precision vector for the quantum affine plane.
"""
def __init__(self, rank=0, sform=None, prec=None):
self._rank = rank
self._sform = sform
self.base = RF('y,q')
self.y = self.base.gen(0)
self.q = self.base.gen(1)
self.gm = 1/self.y - self.y
self.R = TMPoly(self.base, rank, prec=prec)
self._total = None
def __repr__(self):
return f'Wall-crossing structure on a lattice of rank {self.rank()}'
[docs] def total(self, I=None):
"""Total invariant, meaning the stacky invariant for the trivial stability.
EXAMPLE::
sage: from msinvar import *
sage: Q=JordanQuiver(1); Q # Quivers inherit from WCS
Jordan quiver: Quiver with 1 vertices and 1 arrows
sage: Q.prec([2]); # set precision vector
sage: I=Q.total(); I.poly()
1 + (y^2/(y^2 - 1))*x + (y^6/(y^6 - y^4 - y^2 + 1))*x^2
sage: I.Log().poly()
(y^2/(y^2 - 1))*x
"""
if I is not None:
self._total = I
return I
if self._total is None:
self._total = self.total_default()
return self._total
[docs] def rank(self): return self._rank
[docs] def total_default(self): pass
[docs] def twist(self, a, b=None):
"""
Product twist for a pair of vectors or a list of vectors.
"""
if b is not None:
return (-self.y)**self.sform(a, b)
if len(a) <= 1:
return 1
v = vec.add(a)
s = 0
for i in range(len(a)-1):
v = vec.sub(v, a[i])
s += self.sform(a[i], v)
return (-self.y)**s
[docs] def twist_product(self):
"""Twist the product in the quantum affine plane using
:meth:`twist`."""
self.R.prod_twist = self.twist
[docs] def untwist_product(self):
"""Untwist the product in the quantum affine plane
(make it commutative)."""
self.R.prod_twist = None
[docs] def prec(self, d=None):
"""Set or return precision vector for the quantum affine plane."""
return self.R.prec(d)
[docs] def stacky(self, z, I=None, algo='fast'):
"""
Stacky invariant for the stability ``z``.
See :meth:`total2stacky_algo1` for more details.
The value of ``algo`` can be 'fast', 'fast2', 'slow'.
If the total invariant ``I`` is None, we consider :meth:`total`.
EXAMPLES::
sage: from msinvar import *
sage: Q=KroneckerQuiver(2)
sage: Q.prec([5,5])
sage: z=Stability([1,0])
sage: I1=Q.stacky(z,algo='fast')
sage: I2=Q.stacky(z,algo='fast2')
sage: I1([1,1])
(y^2 + 1)/(y^2 - 1)
sage: I1.poly()-I2.poly()
0
"""
if I is None:
I = self.total()
if algo == "fast":
return total2stacky_algo1(self, I, z)
if algo == "fast2":
return total2stacky_algo2(self, I, z)
if algo == "slow":
T = reineke_transform(z)
T.twist = self.twist
return T(I)
raise ValueError("No such algo")
[docs] def stacky_from_total(self, *args, **kw):
"""Alias for :meth:`stacky`."""
return self.stacky(*args, *kw)
[docs] def Omh(self, *args, **kw):
"""Alias for :meth:`stacky`."""
return self.stacky(*args, *kw)
[docs] def rat_from_total(self, z, I=None, algo='fast'):
"""Return rational invariant for the stability ``z``."""
Omh = self.stacky(z, I, algo)
return self.stk2rat(Omh) # assume that z is generic
[docs] def Omb(self, *args, **kw):
"""Alias for :meth:`rat_from_total`."""
return self.rat_from_total(*args, *kw)
[docs] def int_from_total(self, z, I=None, algo='fast'):
"""Return integer invariant for the stability ``z``."""
Omh = self.stacky(z, I, algo)
return self.stk2int(Omh) # assume that z is generic
[docs] def Om(self, *args, **kw):
"""Alias for :meth:`int_from_total`."""
return self.int_from_total(*args, *kw)
[docs] def stacky2total(self, I, z):
"""See :meth:`stacky2total_algo1`."""
return stacky2total_algo1(self, I, z)
[docs] def stk2stk(self, I, z, z1, algo="fast"):
"""
Transform stacky invariant ``I`` for stability ``z`` to the
stacky invariant for stability ``z1``.
The value of ``algo`` can be 'fast' or 'slow'.
"""
if algo == "fast":
I1 = stacky2total_algo1(self, I, z)
return total2stacky_algo1(self, I1, z1)
if algo == "slow":
T = joyce_transform(z, z1)
T.twist = self.twist
return T(I)
[docs] def stk2rat(self, I, z=None):
"""Transform stacky invariant ``I`` to the rational invariant.
By default we assume that ``z`` is a generic stability.
"""
if z is None:
I1 = I.plog()
return Invariant(lambda d: I1(d)*self.gm, self)
T = log_transform(z)
T.twist = lambda l: self.twist(l)*self.gm
return T(I)
[docs] def rat2stk(self, I, z=None):
"""Transform rational invariant ``I`` to the stacky invariant.
By default we assume that ``z`` is a generic stability.
"""
I1 = Invariant(lambda d: I(d)/self.gm, self)
if z is None:
return I1.pexp()
T = exp_transform(z)
T.twist = self.twist
return T(I1)
[docs] def rat2int(self, I):
"""Transform rational invariant ``I`` to the integer invariant."""
def I1(d): return I(d)/self.gm
return Invariant(lambda d: IPsi_map(I1, d)*self.gm, self)
[docs] def int2rat(self, I):
"""Transform integer invariant ``I`` to the rational invariant."""
def I1(d): return I(d)/self.gm
return Invariant(lambda d: Psi_map(I1, d)*self.gm, self)
[docs] def stk2int(self, I):
"""Transform stacky invariant ``I`` to the integer invariant.
We assume that the stability parameter is generic.
"""
I1 = I.pLog()
return Invariant(lambda d: I1(d)*self.gm, self)
[docs] def int2stk(self, I):
"""Transform integer invariant ``I`` to the stacky invariant.
We assume that the stability parameter is generic.
"""
I1 = Invariant(lambda d: I(d)/self.gm, self)
return I1.pExp()
[docs] def poly(self, I, z=None, slope=None):
"""
Transform invariant ``I`` to a polynomial,
considering only degrees having a given ``slope`` with respect to a
given stability parameter ``z``.
"""
return I.poly(self, z, slope)
[docs] def series(self, *args, **kw):
"""Alias for :meth:`poly`."""
return self.poly(*args, **kw)
# quiver related methods (requires Euler form)
[docs] def twist_T(self, d):
"""Auxiliary term twist that depends on :meth:`eform`."""
return (-self.y)**(self.eform(d, d))
[docs] def twist_TI(self, d):
"""Auxiliary term twist that depends on :meth:`eform`."""
return (-self.y)**(-self.eform(d, d))
# end of quiver related methods
[docs] def stable_from_stacky(self, I):
"""
Count stable objects of a fixed slope, assuming that the stacky
invariant ``I`` counting semistable objects of that slope is given.
Based on :arxiv:`0708.1259`.
"""
# f = self.series(I, z, slope)
f = I.poly()
if f.constant_coefficient() == 0:
f = 1+f
self.twist_product() # quantum torus product
f1 = f.invert() # inversion in the quantum torus
self.untwist_product() # restore commutative product
f2 = f1.term_twist(self.twist_TI) # Exp(a/1-q)
f3 = f2.Log()*(1-self.y**2)
return Invariant(f3, self)
[docs] def stable_from_total(self, z, slope=0, I=None):
"""
Count ``z``-stable representations having a given ``slope``, assuming
that the total invariant ``I`` is given.
Based on :arxiv:`0708.1259`.
- ``z`` -- Stability parameter.
- ``slope`` -- Slope value.
- ``I`` -- Total invariant (if None, we consider :meth:`total`).
"""
I1 = self.stacky(z, I).restrict(z, slope)
return self.stable_from_stacky(I1)
[docs] def stable(self, *args, **kw):
"""Alias for :meth:`stable_from_total`."""
return self.stable_from_total(*args, **kw)
[docs] def simple(self, I=None):
"""Count simple reprentations of a quiver, assuming that the total
invariant ``I`` is given. If ``I`` is None, we consider :meth:`total`.
Based on :arxiv:`0708.1259`.
"""
if I is None:
I = self.total()
return self.stable_from_stacky(I)
[docs] def self_stab(self, d):
"""Self-stability for the dimension vector ``d``."""
n = len(d)
a = [self.sform(vec.basis(i, n), d) for i in range(n)]
return Stability(a)
[docs] def attr_stab(self, d):
"""Attractor stability for the dimension vector ``d``."""
z = self.self_stab(d)
while not z.is_generic(d):
z = z.randomize()
return z
# return self.self_stab(d).randomize()
[docs] def stkAtt(self, I=None):
"""Calculate stacky attractor invariant assuming that the total
invariant is ``I``. If ``I`` is None, we consider :meth:`total`."""
def f(d):
z = self.attr_stab(d)
I1 = self.stacky(z, I)
return I1(d)
return Invariant(f, self)
[docs] def ratAtt(self, I=None):
"""Calculate rational attractor invariant assuming that the total
invariant is ``I``. If ``I`` is None, we consider :meth:`total`."""
return self.stk2rat(self.stkAtt(I))
[docs] def intAtt(self, I=None):
"""Calculate integer attractor invariant assuming that the total
invariant is ``I``. If ``I`` is None, we consider :meth:`total`."""
return self.stk2int(self.stkAtt(I))
OmhAtt = stkAtt
OmbAtt = ratAtt
OmAtt = intAtt
[docs] def stkAtt2total(self, I=None):
"""
Calculate total invariant, assuming that the stacky attractor invariant
is ``I``.
This is a recursive inversion of :meth:`stkAtt`.
"""
if I is None:
I = self.stkAtt_default()
T = recursive_inversion(self.stkAtt)
return T(I)
[docs] def ratAtt2total(self, I=None):
"""
Calculate total invariant, assuming that the rational attractor
invariant is ``I``.
"""
if I is None:
I = self.ratAtt_default()
I1 = self.rat2stk(I)
return self.stkAtt2total(I1)
[docs] def intAtt2total(self, I=None):
"""
Calculate total invariant, assuming that the integer attractor invariant
is ``I``.
"""
if I is None:
I = self.intAtt_default()
I1 = self.int2stk(I)
return self.stkAtt2total(I1)
[docs] def intAtt_default(self):
def f(d):
if sum(d) == 1:
return 1
return 0
return Invariant(f, self)
[docs] def ratAtt_default(self):
return self.int2rat(self.intAtt_default())
[docs] def stkAtt_default(self):
return self.int2stk(self.intAtt_default())
WCS = WallCrossingStructure
[docs]def total2stacky_algo1(W, I, z):
"""
Calculate stacky invariant for stability ``z``, assuming that the total
invariant is ``I``.
- ``W`` -- Wall-crossing structure,
- ``I`` -- Invariant,
- ``z``-- Stability.
Based on :arxiv:`math/0204059` (5.5)
and implementation by
`Pieter Belmans <https://github.com/pbelmans/hodge-diamond-cutter>`_.
Has comparable speed to :meth:`total2stacky_algo2`.
"""
z = Stability.check(z)
def stacky(d):
if vec.iszero(d):
return I(d)
te = z.normalize(d)
zero = [0]*len(d)
l = [zero]+list(e for e in IntegerVectors_iterator(d)
if te(e) > 0)+[d]
def T(i, j):
a = vec.sub(l[j], l[i])
if all(k >= 0 for k in a):
return I(a) * W.twist(l[i], a)
return 0
n = len(l)
b = [0]*(n-1)+[1]
x = [0]*n
for i in range(n-1, -1, -1):
x[i] = b[i]-sum(T(i, j)*x[j] for j in range(i+1, n))
return -x[0]
return Invariant(stacky, W)
[docs]def total2stacky_algo2(W, I, z):
"""
Calculate stacky invariant for stability ``z``, assuming that the total
invariant is ``I``.
- ``W`` -- Wall-crossing structure,
- ``I`` -- Invariant,
- ``z``-- Stability.
Has comparable speed to :meth:`total2stacky_algo1`.
"""
z = Stability.check(z)
def stacky(d0):
if vec.iszero(d0):
return I(d0)
te = z.normalize(d0)
@cache
def tail(d):
s = 0
for e in IntegerVectors_iterator(d):
d1 = vec.sub(d, e)
if vec.iszero(d1):
s += I(e)
elif te(d1) < 0:
s -= tail(d1)*I(e)*W.twist(e, d1)
return s
return tail(d0)
return Invariant(stacky, W)
[docs]def stacky2total_algo1(W, I, z):
"""
Calculate total invariant, assuming that the stacky invariant for
stability ``z`` is ``I``.
This algorithm is faster than :meth:`stacky2total_algo2`.
- ``W`` -- Wall-crossing structure,
- ``I`` -- Invariant,
- ``z``-- Stability.
"""
z = Stability.check(z)
@cache
def total(d, slope=None):
if vec.iszero(d):
return I(d)
s = 0
for e in IntegerVectors_iterator(d):
if slope is None or z(e) < slope:
d1 = vec.sub(d, e)
s += total(d1, z(e))*I(e)*W.twist(e, d1)
return s
return Invariant(total, W)
[docs]def stacky2total_algo2(W, I, z):
"""
Calculate total invariant, assuming that the stacky invariant for
stability ``z`` is ``I``.
This is a recursive inversion of :meth:`total2stacky_algo1`.
It is slower than :meth:`stacky2total_algo1`.
- ``W`` -- Wall-crossing structure,
- ``I`` -- Invariant,
- ``z``-- Stability.
"""
def T(I): return total2stacky_algo1(W, I, z)
T1 = recursive_inversion(T)
return T1(I)
# def total2stkAtt(W, I):
# """
# Calculate stacky attractor invariant, assuming that the total invariant
# is ``I``.
# INPUT:
# - ``W`` -- Wall-crossing structure,
# - ``I`` -- Invariant.
# """
# def f(d):
# z = W.attr_stab(d)
# return total2stacky_algo1(W, z, I)(d)
# return Invariant(f, W)
# def total2intAtt(W, I):
# """
# Calculate integer attractor invariant, assuming that the total invariant
# is ``I``.
# INPUT:
# - ``W`` -- Wall-crossing structure,
# - ``I`` -- Invariant.
# """
# I = total2stkAtt(W, I).pLog()
# return Invariant(lambda d: I(d)*W.gm, W)
# def stacky2total_algo2(W, I, z):
# """
# Calculate total invariant, assuming that the stacky invariant for
# stability ``z`` is ``I``.
# This is a recursive inversion of total2stacky. It is slower than
# :meth:`stacky2total_algo1`.
# INPUT:
# - ``W`` -- Wall-crossing structure,
# - ``I`` -- Invariant,
# - ``z``-- Stability.
# """
# z = Stability.check(z)
# @cache
# def total(d0):
# if vec.iszero(d0):
# return 1
# c = z(d0)
# @cache
# def tail(d):
# s = 0
# for e in IntegerVectors_iterator(d):
# d1 = vec.sub(d, e)
# if vec.iszero(d1):
# s += total(e)
# elif z(d1) < c:
# s -= tail(d1)*total(e)*W.twist(e, d1)
# return s
# s = 0
# for e in IntegerVectors_iterator(d0):
# d1 = vec.sub(d0, e)
# if vec.iszero(d1):
# pass
# elif z(d1) < c:
# s -= tail(d1)*total(e)*W.twist(e, d1)
# return I(d0)-s
# return Invariant(total, W)
# def stkAtt2total_algo(W, I):
# """
# Calculate total invariant, assuming that the stacky invariant for
# stability ``z`` is ``I``.
# This is a recursive inversion of total2stacky. It is slower than
# :meth:`stacky2total_algo1`.
# INPUT:
# - ``W`` -- Wall-crossing structure,
# - ``I`` -- Invariant,
# - ``z``-- Stability.
# """
# @cache
# def total(d0):
# if vec.iszero(d0):
# return 1
# z = W.attr_stab(d0)
# te = z.normalize(d0)
# @cache
# def tail(d):
# s = 0
# for e in IntegerVectors_iterator(d):
# d1 = vec.sub(d, e)
# if vec.iszero(d1):
# s += total(e)
# elif te(d1) < 0:
# s -= tail(d1)*total(e)*W.twist(e, d1)
# return s
# s = 0
# for e in IntegerVectors_iterator(d0):
# d1 = vec.sub(d0, e)
# if vec.iszero(d1):
# pass
# elif te(d1) < 0:
# s -= tail(d1)*total(e)*W.twist(e, d1)
# return I(d0)-s
# return Invariant(total, W)