Skip to content

Commit 4a74567

Browse files
committed
implement natural projection from cl(f²D) to cl(D)
1 parent 4881b0f commit 4a74567

File tree

1 file changed

+207
-0
lines changed

1 file changed

+207
-0
lines changed

src/sage/quadratic_forms/bqf_class_group.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,15 @@
8282
from sage.structure.parent import Parent
8383
from sage.structure.unique_representation import UniqueRepresentation
8484
from sage.structure.element import AdditiveGroupElement
85+
from sage.categories.morphism import Morphism
8586

8687
from sage.misc.prandom import randrange
8788
from sage.rings.integer_ring import ZZ
89+
from sage.rings.finite_rings.integer_mod_ring import Zmod
8890
from sage.rings.finite_rings.integer_mod import Mod
91+
from sage.rings.polynomial.polynomial_ring import polygen
8992
from sage.arith.misc import random_prime
93+
from sage.matrix.constructor import matrix
9094
from sage.groups.generic import order_from_multiple, multiple
9195
from sage.groups.additive_abelian.additive_abelian_wrapper import AdditiveAbelianGroupWrapper
9296
from sage.quadratic_forms.binary_qf import BinaryQF
@@ -330,6 +334,34 @@ def gens(self):
330334
"""
331335
return [g.element() for g in self.abelian_group().gens()]
332336

337+
def _coerce_map_from_(self, other):
338+
r"""
339+
Return the natural projection map between two class groups
340+
of binary quadratic forms when it is defined.
341+
342+
.. SEEALSO:: :class:`BQFClassGroupQuotientMorphism`
343+
344+
EXAMPLES::
345+
346+
sage: G = BQFClassGroup(-4*117117)
347+
sage: H = BQFClassGroup(-4*77)
348+
sage: proj = G.hom(H); proj # implicit doctest
349+
Coercion morphism:
350+
From: Form Class Group of Discriminant -468468
351+
To: Form Class Group of Discriminant -308
352+
sage: elt = G(BinaryQF(333, 306, 422)); elt
353+
Class of 333*x^2 + 306*x*y + 422*y^2
354+
sage: proj(elt)
355+
Class of 9*x^2 + 4*x*y + 9*y^2
356+
"""
357+
if not isinstance(other, BQFClassGroup):
358+
return None
359+
try:
360+
proj = BQFClassGroupQuotientMorphism(other, self)
361+
except (TypeError, ValueError):
362+
return None
363+
return proj
364+
333365

334366
class BQFClassGroup_element(AdditiveGroupElement):
335367
r"""
@@ -611,3 +643,178 @@ def order(self):
611643
2
612644
"""
613645
return order_from_multiple(self, self.parent().cardinality())
646+
647+
648+
def _project_bqf(bqf, q):
649+
r"""
650+
Internal helper function to compute the image of a
651+
:class:`BQFClassGroup_element` of discriminant `D`
652+
in the form class group of discriminant `D/q^2`.
653+
654+
EXAMPLES::
655+
656+
sage: from sage.quadratic_forms.bqf_class_group import _project_bqf
657+
sage: f1 = BinaryQF([4, 2, 105])
658+
sage: f2 = _project_bqf(f1, 2); f2
659+
x^2 + x*y + 105*y^2
660+
sage: f1.discriminant().factor()
661+
-1 * 2^2 * 419
662+
sage: f2.discriminant().factor()
663+
-1 * 419
664+
665+
::
666+
667+
sage: f1 = BinaryQF([109, 92, 113])
668+
sage: f2 = _project_bqf(f1, 101); f2
669+
53*x^2 - 152*x*y + 109*y^2
670+
sage: f1.discriminant().factor()
671+
-1 * 2^2 * 101^2
672+
sage: f2.discriminant().factor()
673+
-1 * 2^2
674+
675+
ALGORITHM: Find a class representative with `q^2 \mid a`
676+
(and `q\mid b`) and substitute `x\mapsto x/q`.
677+
"""
678+
q2 = q**2
679+
disc = bqf.discriminant()
680+
if not q2.divides(disc) or disc//q2 % 4 not in (0,1):
681+
raise ValueError('discriminant not divisible by q^2')
682+
683+
a,b,c = bqf
684+
685+
# lucky case: q^2|c (and q|b)
686+
if q2.divides(c):
687+
a,b,c = c,-b,a
688+
689+
# general case: neither q^2|a nor q^2|c
690+
elif not q2.divides(a):
691+
692+
# represent some multiple of q^2
693+
R = Zmod(q2)
694+
x = polygen(R)
695+
for v in R:
696+
eq = a*x**2 + b*x*v + c*v**2
697+
try:
698+
u = eq.any_root()
699+
except (ValueError, IndexError): # why IndexError? see #37034
700+
continue
701+
if u or v:
702+
break
703+
else:
704+
assert False
705+
706+
# find equivalent form with q^2|a (and q|b)
707+
u,v = map(ZZ, (u,v))
708+
assert q2.divides(bqf(u,v))
709+
if not v:
710+
v += q
711+
g,r,s = u.xgcd(v)
712+
assert g.is_one()
713+
M = matrix(ZZ, [[u,-v],[s,r]])
714+
assert M.det().is_one()
715+
a,b,c = bqf * M
716+
717+
# remaining case: q^2|a (and q|b)
718+
assert q2.divides(a)
719+
assert q.divides(b)
720+
return BinaryQF(a//q2, b//q, c)
721+
722+
class BQFClassGroupQuotientMorphism(Morphism):
723+
r"""
724+
Let `D` be a discriminant and `f > 0` an integer.
725+
726+
Given the class groups `G` and `H` of discriminants `f^2 D` and `D`,
727+
this class represents the natural projection morphism `G \to H` which
728+
is defined by finding a class representative `[a,b,c]` satisfying
729+
`f^2 \mid a` and `f \mid b` and substituting `x \mapsto x/f`.
730+
731+
Alternatively, one may pass the discriminants `f^2 D` and `D` instead
732+
of the :class:`BQFClassGroup` objects `G` and `H`.
733+
734+
This map is a well-defined group homomorphism.
735+
736+
EXAMPLES::
737+
738+
sage: from sage.quadratic_forms.bqf_class_group import BQFClassGroupQuotientMorphism
739+
sage: G = BQFClassGroup(-4*117117)
740+
sage: H = BQFClassGroup(-4*77)
741+
sage: proj = BQFClassGroupQuotientMorphism(G, H)
742+
sage: elt = G(BinaryQF(333, 306, 422))
743+
sage: proj(elt)
744+
Class of 9*x^2 + 4*x*y + 9*y^2
745+
746+
TESTS:
747+
748+
Check that it is really a group homomorphism::
749+
750+
sage: D = -randrange(1, 10^4)
751+
sage: D *= 4 if D%4 not in (0,1) else 1
752+
sage: f = randrange(1, 10^3)
753+
sage: G = BQFClassGroup(f^2*D)
754+
sage: H = BQFClassGroup(D)
755+
sage: proj = G.hom(H)
756+
sage: proj(G.zero()) == H.zero()
757+
True
758+
sage: elt1 = G.random_element()
759+
sage: elt2 = G.random_element()
760+
sage: proj(elt1 + elt2) == proj(elt1) + proj(elt2)
761+
True
762+
"""
763+
def __init__(self, G, H):
764+
r"""
765+
Initialize this morphism between class groups of binary
766+
quadratic forms.
767+
768+
EXAMPLES::
769+
770+
sage: from sage.quadratic_forms.bqf_class_group import BQFClassGroupQuotientMorphism
771+
sage: G = BQFClassGroup(-4*117117)
772+
sage: H = BQFClassGroup(-4*77)
773+
sage: BQFClassGroupQuotientMorphism(G, H)
774+
Generic morphism:
775+
From: Form Class Group of Discriminant -468468
776+
To: Form Class Group of Discriminant -308
777+
"""
778+
if not isinstance(G, BQFClassGroup):
779+
try:
780+
disc = ZZ(G)
781+
except Exception:
782+
raise TypeError('G needs to be a BQFClassGroup')
783+
G = BQFClassGroup(disc)
784+
if not isinstance(H, BQFClassGroup):
785+
try:
786+
disc = ZZ(G)
787+
except Exception:
788+
raise TypeError('H needs to be a BQFClassGroup')
789+
H = BQFClassGroup(disc)
790+
try:
791+
self.f = ZZ((G.discriminant() / H.discriminant()).sqrt(extend=False)).factor()
792+
except ValueError:
793+
raise ValueError('morphism only defined when disc(G) = f^2 * disc(H)')
794+
super().__init__(G, H)
795+
796+
def _call_(self, elt):
797+
r"""
798+
Evaluate this morphism.
799+
800+
EXAMPLES::
801+
802+
sage: from sage.quadratic_forms.bqf_class_group import BQFClassGroupQuotientMorphism, _project_bqf
803+
sage: G = BQFClassGroup(-4*117117)
804+
sage: H = BQFClassGroup(-4*77)
805+
sage: proj = BQFClassGroupQuotientMorphism(G, H)
806+
sage: elt = G(BinaryQF(333, 306, 422))
807+
sage: proj(elt)
808+
Class of 9*x^2 + 4*x*y + 9*y^2
809+
sage: proj(elt) == H(_project_bqf(_project_bqf(elt.form(), 3), 13))
810+
True
811+
sage: proj(elt) == H(_project_bqf(_project_bqf(elt.form(), 13), 3))
812+
True
813+
814+
ALGORITHM: Repeated application of :func:`_project_bqf` for the prime factors in `f`.
815+
"""
816+
bqf = elt.form()
817+
for q,m in self.f:
818+
for _ in range(m):
819+
bqf = _project_bqf(bqf, q)
820+
return self.codomain()(bqf)

0 commit comments

Comments
 (0)