A tutorial on how to use thetAV in SageMath#
With this notebook we introduce the main functionalities of the thetAV package.
Table of contents#
Introduction#
To import the functionalities of this package, start with the following line:
[2]:
from thetAV import *
The following cell shows how to create an Abelian Variety with theta structure, by giving its Theta Null point.
[3]:
FF = GF(331); n = 2; g = 2
pt = [328 , 213 , 75 , 1]
A = KummerVariety(FF, g, pt)
It is possible to check that the given point is a valid Theta Null point. It checks that the given data satisfies the Riemann Relations. This is not tested unless it is specified.
[4]:
B = AbelianVariety(GF(331), 4, 1, [26, 191, 70, 130]); B
[4]:
Abelian variety of dimension 1 with theta null point (26 : 191 : 70 : 130) defined over Finite Field of size 331
[5]:
B = AbelianVariety(GF(331), 4, 1, [26, 191, 70, 130], check=True)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/tmp/ipykernel_44692/81372161.py in <cell line: 1>()
----> 1 B = AbelianVariety(GF(Integer(331)), Integer(4), Integer(1), [Integer(26), Integer(191), Integer(70), Integer(130)], check=True)
~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/thetAV/constructor.py in AbelianVariety(*data, **kwargs)
86 data = data[:1] + data[2:]
87 return theta_null_point.KummerVariety(*data, **kwargs)
---> 88 return theta_null_point.AbelianVariety_ThetaStructure(*data, **kwargs)
89
90 return ModularAbelianVariety(data[0])
~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/thetAV/theta_null_point.py in __init__(self, R, n, g, T, check)
532
533 if any(T[idx(-i)] != val for i, val in zip(D, T)):
--> 534 raise ValueError('The given list does not define a valid thetanullpoint')
535
536 for (idxi, i), (idxj, j) in product(enumerate(D), repeat=2):
ValueError: The given list does not define a valid thetanullpoint
The following cell shows how to define a point in the constructed abelian variety.
[6]:
P0 = A(0)
P = A([255 , 89 , 30 , 1])
As with the theta null point, it is possible to check that the given data defines a valid point, but it is not tested unless it is specified. For that we need to use the AbelianVariety method point.
[7]:
Q = B([1 , 1 , 1 , 1])
[8]:
Q = B.point([1 , 1 , 1 , 1], check=True)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/tmp/ipykernel_44692/3498440664.py in <cell line: 1>()
----> 1 Q = B.point([Integer(1) , Integer(1) , Integer(1) , Integer(1)], check=True)
~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/thetAV/theta_null_point.py in point(self, P, **kwds)
235 AP = A(P)
236 return AP.to_algebraic(A=self)
--> 237 return self._point(self, P, **kwds)
238
239 __call__ = point
~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/thetAV/theta_point.py in __init__(self, X, v, check)
788 if check:
789 O = X.theta_null_point()
--> 790 idx = partial(tools.idx, n=O.level())
791 dual = X._dual
792 D = X._D
~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/sage/structure/element.pyx in sage.structure.element.Element.__getattr__ (build/cythonized/sage/structure/element.c:4826)()
492 AttributeError: 'LeftZeroSemigroup_with_category.element_class' object has no attribute 'blah_blah'
493 """
--> 494 return self.getattr_from_category(name)
495
496 cdef getattr_from_category(self, name):
~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/sage/structure/element.pyx in sage.structure.element.Element.getattr_from_category (build/cythonized/sage/structure/element.c:4938)()
505 else:
506 cls = P._abstract_element_class
--> 507 return getattr_from_other_class(self, cls, name)
508
509 def __dir__(self):
~/anaconda3/envs/sage-cgf/lib/python3.10/site-packages/sage/cpython/getattr.pyx in sage.cpython.getattr.getattr_from_other_class (build/cythonized/sage/cpython/getattr.c:2702)()
359 dummy_error_message.cls = type(self)
360 dummy_error_message.name = name
--> 361 raise AttributeError(dummy_error_message)
362 attribute = <object>attr
363 # Check for a descriptor (__get__ in Python)
AttributeError: 'AbelianVarietyPoint' object has no attribute 'level'
You can access a given coordinate using the corresponding element of \((\mathbb{Z}/n\mathbb{Z})^g\):
[9]:
P[[1,0]]
[9]:
89
Basic arithmetic#
Follows the example in Section 6 of ‘Efficient Pairing Computation with theta functions’ by David Lubicz and Damien Robert.
[10]:
l = 1889
lP = l*P; lP
lP == A(0) #as projective points
[10]:
True
[11]:
R.<X> = PolynomialRing(FF)
poly = X^4 + 3*X^2 + 290*X + 3
FF2.<t> = poly.splitting_field()
Q_list = [158*t^3 + 67*t^2 + 9*t + 293, 290*t^3 + 25*t^2 + 235*t + 280,
155*t^3 + 84*t^2 + 15*t + 170, 1]
A2 = A.change_ring(FF2)
P = A2(P)
Q = A2(Q_list)
P + Q #returns P + Q and P - Q
[11]:
((221*t^3 + 178*t^2 + 126*t + 27 : 32*t^3 + 17*t^2 + 175*t + 171 : 180*t^3 + 188*t^2 + 161*t + 119 : 261*t^3 + 107*t^2 + 37*t + 135),
(1 : 56*t^3 + 312*t^2 + 147*t + 287 : 277*t^3 + 295*t^2 + 7*t + 287 : 290*t^3 + 203*t^2 + 274*t + 10))
[12]:
PmQ_list = (62*t^3 + 16*t^2 + 255*t + 129 , 172*t^3 + 157*t^2 + 43*t + 222 ,
258*t^3 + 39*t^2 + 313*t + 150 , 1)
PmQ = A2.point(PmQ_list)
PQ = Q.diff_add(P, PmQ); PQ
[12]:
(261*t^3 + 107*t^2 + 37*t + 135 : 205*t^3 + 88*t^2 + 195*t + 125 : 88*t^3 + 99*t^2 + 164*t + 98 : 159*t^3 + 279*t^2 + 254*t + 276)
[13]:
P.weil_pairing(l, Q, PQ)
[13]:
17*t^3 + 153*t^2 + 305*t + 187
[14]:
lPQ, lP = P.diff_multadd(l, PQ, Q)
PlQ, lQ = Q.diff_multadd(l, PQ, P)
P._weil_pairing_from_points(Q, lP, lQ, lPQ, PlQ)
[14]:
17*t^3 + 153*t^2 + 305*t + 187
Computing points from other data#
This section focuses on the computation of morphisms between hyperelliptic curves and the corresponding abelian varieties (their jacobians) with theta functions of level 2 and 4.
We can define a curve and its Jacobian
[18]:
F = GF(83^2); z, = F.gens(); Fx.<x> = PolynomialRing(F)
g = 2
a = [F(0), 1, 3, 15, 20]
rac = sqrt(a[1] - a[0])
f = prod(x - al for al in a)
C = HyperellipticCurve(f); C
[18]:
Hyperelliptic Curve over Finite Field in z2 of size 83^2 defined by y^2 = x^5 + 44*x^4 + 28*x^3 + 23*x^2 + 70*x
The most natural way to construct the corresponding Abelian Variety is with the function AbelianVariety.from_curve:
[19]:
A = AbelianVariety.from_curve(C); A
[19]:
Abelian variety of dimension 2 with theta null point (68 : z2 + 33 : 46 : z2 + 33 : 2*z2 + 29 : 77*z2 + 58 : 81*z2 + 31 : 38*z2 + 16 : 8 : 67*z2 + 53 : 48 : 67*z2 + 53 : 2*z2 + 29 : 38*z2 + 16 : 81*z2 + 31 : 77*z2 + 58) defined over Finite Field in z2 of size 83^2
Alternatively, if we have the classical theta constants associated to the Jacobian, we can also use AbelianVariety.with_theta_basis('F(2,2)')
[20]:
thc = [0]*(2**(2*g))
idx = lambda x : ZZ(x, 2)
thc[idx([0,0,0,0])]=F(1)
thc[idx([0,0,1,1])]=z^1491
thc[idx([0,0,1,0])]=z^777
thc[idx([0,0,0,1])]=F(30)
thc[idx([1,0,0,0])]=F(37)
thc[idx([1,0,0,1])]=z^2058
thc[idx([0,1,0,0])]=F(56)
thc[idx([1,1,0,0])]=F(57)
thc[idx([0,1,1,0])]=z^609
thc[idx([1,1,1,1])]=z^1533
thc[idx([0,1,0,1])]=F(0)
thc[idx([0,1,1,1])]=F(0)
thc[idx([1,0,1,0])]=F(0)
thc[idx([1,1,1,0])]=F(0)
thc[idx([1,0,1,1])]=F(0)
thc[idx([1,1,0,1])]=F(0)
thc = AbelianVariety.with_theta_basis('F(2,2)', F, 4, g, thc, curve=C, wp=a, rac=rac)
Now we can map some points between Mumford and Theta representation.
We define the jacobian of C and consider the Mumford divisor defined by:
[21]:
J = Jacobian(C)
u = (x-43)*(x-10); v = z^954*x + z^2518;
D = J([u,v]); D
[21]:
(x^2 + 30*x + 15, y + (21*z2 + 71)*x + 10*z2 + 10)
Then we can simply create a point of the abelian variety with this data:
[22]:
thD = thc(D);
Note that, even if internally we use the classical basis for these computations, the result is always given with basis \(\mathcal{F}(4)\):
[23]:
thD.abelian_variety()
[23]:
Abelian variety of dimension 2 with theta null point (68 : z2 + 33 : 46 : z2 + 33 : 2*z2 + 29 : 77*z2 + 58 : 81*z2 + 31 : 38*z2 + 16 : 8 : 67*z2 + 53 : 48 : 67*z2 + 53 : 2*z2 + 29 : 38*z2 + 16 : 81*z2 + 31 : 77*z2 + 58) defined over Finite Field in z2 of size 83^2
But one can always recover the point in terms of the basis \(\mathcal{F}(2,2)\) as follows:
[39]:
thD.with_theta_basis('F(2,2)')
[39]:
(78*z2 + 13 : 77*z2 + 26 : 43*z2 + 3 : 54*z2 + 67 : 77*z2 + 61 : 35*z2 + 2 : 31*z2 + 8 : 19*z2 + 38 : 25*z2 + 9 : z2 + 65 : 17*z2 + 75 : 18*z2 + 38 : 50*z2 + 17 : 41*z2 + 6 : 18*z2 + 48 : 39*z2 + 73)
Conversely, if we define the theta point
[24]:
th = [0]*(2**(2*g))
th[idx([0,0,0,0])] = z^1755
th[idx([0,0,1,1])] = z^1179
th[idx([0,0,1,0])] = z^977
th[idx([0,0,0,1])] = z^1105
th[idx([1,0,0,0])] = z^352
th[idx([1,0,0,1])] = z^1674
th[idx([0,1,0,0])] = z^2523
th[idx([1,1,0,0])] = z^5890
th[idx([0,1,1,0])] = z^5051
th[idx([1,1,1,1])] = z^5243
th[idx([0,1,0,1])] = z^4021
th[idx([0,1,1,1])] = z^4716
th[idx([1,0,1,0])] = z^139
th[idx([1,1,1,0])] = z^507
th[idx([1,0,1,1])] = z^2832
th[idx([1,1,0,1])] = z^3382
th = thc(th, basis='F(2,2)')
The function Level4ThetaPointToMumford returns the corresponding Mumford polynomials
[25]:
from thetAV.morphisms_level4 import Level4ThetaPointToMumford
u,v = Level4ThetaPointToMumford(a, rac, th.with_theta_basis('F(2,2)'))
D == J([u, v])
[25]:
True
In level 2#
First we define the curve and its Kummer surface
A curve y² = f(x) is defined by a list a containing the roots of f(x); it is important that f be of odd degree and a be ordered (the Theta constants depend on this ordering).
First defined the curve and the Kummer Surface
[27]:
F = GF(83^2); z, = F.gens(); Fx.<x> = PolynomialRing(F)
g = 2;
a = [F(el) for el in [0,1,3,15,20]]
f = prod(x - al for al in a)
C = HyperellipticCurve(f); C
[27]:
Hyperelliptic Curve over Finite Field in z2 of size 83^2 defined by y^2 = x^5 + 44*x^4 + 28*x^3 + 23*x^2 + 70*x
The Theta constants of the Kummer surface.
[28]:
thc2 = [0]*(2**(2*g))
idx = lambda x : ZZ(x, 2)
thc2[idx([0,0,0,0])] = F(1)
thc2[idx([0,0,1,1])] = z^2982
thc2[idx([0,0,1,0])] = z^1554
thc2[idx([0,0,0,1])] = F(70)
thc2[idx([1,0,0,0])] = F(41)
thc2[idx([1,0,0,1])] = F(76)
thc2[idx([0,1,0,0])] = F(65)
thc2[idx([1,1,0,0])] = F(12)
thc2[idx([0,1,1,0])] = z^1218
thc2[idx([1,1,1,1])] = z^3066
thc2[idx([0,1,0,1])] = F(0)
thc2[idx([0,1,1,1])] = F(0)
thc2[idx([1,0,1,0])] = F(0)
thc2[idx([1,1,1,0])] = F(0)
thc2[idx([1,0,1,1])] = F(0)
thc2[idx([1,1,0,1])] = F(0)
thc2 = KummerVariety.with_theta_basis('F(2,2)^2', F, 2, g, thc2, curve=C, wp=a)
Now we can map points between Mumford and Theta representations. Consider the Mumford divisor defined by:
[31]:
J = Jacobian(C)
u = (x-43)*(x-10); v2 = (z^954*x + z^2518)^2;
D = J([u,v]); D
[31]:
(x^2 + 30*x + 15, y + (21*z2 + 71)*x + 10*z2 + 10)
And as we did for level 4, we compute the corresponding point.
[32]:
th2D = thc2(D)
Conversely, define the Theta functions
[34]:
th2 = [0]*(2**(2*g))
th2[idx([0,0,0,0])] = z^3608
th2[idx([0,0,1,1])] = z^5026
th2[idx([0,0,1,0])] = z^1654
th2[idx([0,0,0,1])] = z^6408
th2[idx([1,0,0,0])] = z^5576
th2[idx([1,0,0,1])] = z^3952
th2[idx([0,1,0,0])] = z^734
th2[idx([1,1,0,0])] = z^2674
th2[idx([0,1,1,0])] = z^3262
th2[idx([1,1,1,1])] = z^5436
th2[idx([0,1,0,1])] = F(82)
th2[idx([0,1,1,1])] = z^6258
th2[idx([1,0,1,0])] = z^4746
th2[idx([1,1,1,0])] = z^798
th2[idx([1,0,1,1])] = z^5082
th2[idx([1,1,0,1])] = F(2)
th2 = thc2(th2, basis='F(2,2)^2')
The function Level2ThetaPointToMumford returns the corresponding Mumford polynomials (u, v²)
[38]:
from thetAV.morphisms_level2 import Level2ThetaPointToMumford
uth,v2th = Level2ThetaPointToMumford(a, th2.with_theta_basis('F(2,2)^2'))
D == J([uth, sqrt(v2th)])
[38]:
True