From d40de259097c5e8d8fd35539560ca7c3d47523e7 Mon Sep 17 00:00:00 2001 From: Ryan Rueger Date: Sat, 1 Mar 2025 20:25:41 +0100 Subject: Initial Commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Damien Robert Co-Authored-By: Frederik Vercauteren Co-Authored-By: Jonathan Komada Eriksen Co-Authored-By: Pierrick Dartois Co-Authored-By: Riccardo Invernizzi Co-Authored-By: Ryan Rueger [0.01s] Co-Authored-By: Benjamin Wesolowski Co-Authored-By: Arthur Herlédan Le Merdy Co-Authored-By: Boris Fouotsa --- theta_lib/theta_structures/Theta_dim4.py | 351 +++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 theta_lib/theta_structures/Theta_dim4.py (limited to 'theta_lib/theta_structures/Theta_dim4.py') diff --git a/theta_lib/theta_structures/Theta_dim4.py b/theta_lib/theta_structures/Theta_dim4.py new file mode 100644 index 0000000..7851c56 --- /dev/null +++ b/theta_lib/theta_structures/Theta_dim4.py @@ -0,0 +1,351 @@ +from sage.all import * +from sage.structure.element import get_coercion_model + +from .theta_helpers_dim4 import ( + hadamard, + batch_inversion, + product_theta_point_dim4, + product_to_theta_points_dim4, + product_theta_point_dim2_dim4, + product_to_theta_points_dim4_dim2, + act_point, + squared, +) +from .Theta_dim1 import ThetaStructureDim1 +from .Tuple_point import TuplePoint +from ..basis_change.base_change_dim4 import ( + apply_base_change_theta_dim4, + random_symplectic_matrix, + base_change_theta_dim4, +) + +cm = get_coercion_model() + + +class ThetaStructureDim4: + def __init__(self,null_point,null_point_dual=None,inv_null_point_dual_sq=None): + r""" + INPUT: + - null_point: theta-constants. + - inv_null_point_dual_sq: inverse of the squares of dual theta-constants, if provided + (meant to prevent duplicate computation, since this data is already computed when the + codomain of an isogeny is computed). + """ + if not len(null_point) == 16: + raise ValueError("Entry null_point should have 16 coordinates.") + + self._base_ring = cm.common_parent(*(c.parent() for c in null_point)) + self._point = ThetaPointDim4 + self._null_point = self._point(self, null_point) + self._null_point_dual=null_point_dual + self._inv_null_point=None + self._inv_null_point_dual_sq=inv_null_point_dual_sq + + def null_point(self): + """ + """ + return self._null_point + + def null_point_dual(self): + if self._null_point_dual==None: + self._null_point_dual=hadamard(self._null_point.coords()) + return self._null_point_dual + + def base_ring(self): + """ + """ + return self._base_ring + + def zero(self): + """ + """ + return self.null_point() + + def zero_dual(self): + return self.null_point_dual() + + def __repr__(self): + return f"Theta structure over {self.base_ring()} with null point: {self.null_point()}" + + def __call__(self,coords): + return self._point(self,coords) + + def act_null(self,I,J): + r""" + Point of 2-torsion. + + INPUT: + - I, J: two 4-tuples of indices in {0,1}. + + OUTPUT: the action of (I,\chi_J) on the theta null point given by: + (I,\chi_J)*P=(\chi_J(I+K)^{-1}P[I+K])_K + """ + return self.null_point().act_point(I,J) + + def is_K2(self,B): + r""" + Given a symplectic decomposition A[2]=K_1\oplus K_2 canonically + induced by the theta-null point, determines if B is the canonical + basis of K_2 given by act_nul(0,\delta_i)_{1\leq i\leq 4}. + + INPUT: + - B: Basis of 4 points of 2-torsion. + + OUTPUT: Boolean True if and only if B is the canonical basis of K_2. + """ + I0=(0,0,0,0) + if B[0]!=self.act_null(I0,(1,0,0,0)): + return False + if B[1]!=self.act_null(I0,(0,1,0,0)): + return False + if B[2]!=self.act_null(I0,(0,0,1,0)): + return False + if B[3]!=self.act_null(I0,(0,0,0,1)): + return False + return True + + def base_change_struct(self,N): + null_coords=self.null_point().coords() + new_null_coords=apply_base_change_theta_dim4(N,null_coords) + return ThetaStructureDim4(new_null_coords) + + def base_change_coords(self,N,P): + coords=P.coords() + new_coords=apply_base_change_theta_dim4(N,coords) + return self.__call__(new_coords) + + #@cached_method + def _arithmetic_precomputation(self): + r""" + Initializes the precomputation containing the inverse of the theta-constants in standard + and dual (Hadamard transformed) theta-coordinates. Assumes no theta-constant is zero. + """ + O=self.null_point() + if all([O[k]!=0 for k in range(16)]): + self._inv_null_point=batch_inversion(O.coords()) + if self._inv_null_point_dual_sq==None: + U_chi_0_sq=hadamard(squared(O.coords())) + if all([U_chi_0_sq[k]!=0 for k in range(16)]): + self._inv_null_point_dual_sq=batch_inversion(U_chi_0_sq) + self._arith_base_change=False + else: + self._arith_base_change=True + self._arithmetic_base_change() + #print("Zero dual theta constants.\nRandom symplectic base change is being used for duplication.\nDoublings are more costly than expected.") + else: + self._arith_base_change=True + self._arithmetic_base_change() + #print("Zero theta constants.\nRandom symplectic base change is being used for duplication.\nDoublings are more costly than expected.") + + def _arithmetic_base_change(self,max_iter=50): + F=self._base_ring + if F.degree() == 2: + i=self._base_ring.gen() + else: + assert(F.degree() == 1) + Fp2 = GF((F.characteristic(), 2), name='i', modulus=var('x')**2 + 1) + i=Fp2.gen() + + count=0 + O=self.null_point() + while count