A tutorial on how to use AVIsogenies in SageMath
With this notebook we introduce the main functionalities of the AVIsogenies package.
Table of contents
Introduction
To import the functionalities of this package, start with the following line:
[2]:
from avisogenies_sage 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/avisogenies_sage/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/avisogenies_sage/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/avisogenies_sage/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/avisogenies_sage/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 avisogenies_sage.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 avisogenies_sage.morphisms_level2 import Level2ThetaPointToMumford
uth,v2th = Level2ThetaPointToMumford(a, th2.with_theta_basis('F(2,2)^2'))
D == J([uth, sqrt(v2th)])
[38]:
True