Skip to content

Commit e3fd6ec

Browse files
author
Release Manager
committed
gh-37074: implement natural morphism from cl(f²D) to cl(D) ...for class groups of binary quadratic forms, which we have since #36184. This map is defined by finding a class representative $[a,b,c]$ for which $f^2 \mid a$ (and $f \mid b$) and then applying the substitution $x \mapsto x/f$. In the code, we do it in prime steps, since that makes finding a suitable representative a little easier. This can be used, among other things, for determining the kernel of the surjection $\mathrm{cl}(f^2D) \twoheadrightarrow \mathrm{cl}(D)$. URL: #37074 Reported by: Lorenz Panny Reviewer(s): Lorenz Panny, Travis Scrimshaw
2 parents 157a645 + b75a9a4 commit e3fd6ec

File tree

1 file changed

+198
-1
lines changed

1 file changed

+198
-1
lines changed

src/sage/quadratic_forms/bqf_class_group.py

Lines changed: 198 additions & 1 deletion
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 super()._coerce_map_from_(other)
359+
try:
360+
proj = BQFClassGroupQuotientMorphism(other, self)
361+
except (TypeError, ValueError):
362+
return super()._coerce_map_from_(other)
363+
return proj
364+
333365

334366
class BQFClassGroup_element(AdditiveGroupElement):
335367
r"""
@@ -376,7 +408,7 @@ def __init__(self, F, parent, *, check=True, reduce=True):
376408
if not F.is_primitive():
377409
raise ValueError('given quadratic form is not primitive')
378410
if not F.is_positive_definite():
379-
raise NotImplemented('only positive definite forms are currently supported')
411+
raise NotImplementedError('only positive definite forms are currently supported')
380412
if reduce:
381413
F = F.reduced_form()
382414
self._form = F
@@ -611,3 +643,168 @@ 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+
ALGORITHM: Find a class representative with `q^2 \mid a`
655+
(and `q \mid b`) and substitute `x\mapsto x/q`.
656+
657+
EXAMPLES::
658+
659+
sage: from sage.quadratic_forms.bqf_class_group import _project_bqf
660+
sage: f1 = BinaryQF([4, 2, 105])
661+
sage: f2 = _project_bqf(f1, 2); f2
662+
x^2 + x*y + 105*y^2
663+
sage: f1.discriminant().factor()
664+
-1 * 2^2 * 419
665+
sage: f2.discriminant().factor()
666+
-1 * 419
667+
668+
::
669+
670+
sage: f1 = BinaryQF([109, 92, 113])
671+
sage: f2 = _project_bqf(f1, 101); f2
672+
53*x^2 - 152*x*y + 109*y^2
673+
sage: f1.discriminant().factor()
674+
-1 * 2^2 * 101^2
675+
sage: f2.discriminant().factor()
676+
-1 * 2^2
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: f = BQFClassGroupQuotientMorphism(G, H)
774+
sage: TestSuite(f).run(skip='_test_category')
775+
"""
776+
if not isinstance(G, BQFClassGroup):
777+
raise TypeError('G needs to be a BQFClassGroup')
778+
if not isinstance(H, BQFClassGroup):
779+
raise TypeError('H needs to be a BQFClassGroup')
780+
try:
781+
self.f = ZZ((G.discriminant() / H.discriminant()).sqrt(extend=False)).factor()
782+
except ValueError:
783+
raise ValueError('morphism only defined when disc(G) = f^2 * disc(H)')
784+
super().__init__(G, H)
785+
786+
def _call_(self, elt):
787+
r"""
788+
Evaluate this morphism.
789+
790+
EXAMPLES::
791+
792+
sage: from sage.quadratic_forms.bqf_class_group import BQFClassGroupQuotientMorphism, _project_bqf
793+
sage: G = BQFClassGroup(-4*117117)
794+
sage: H = BQFClassGroup(-4*77)
795+
sage: proj = BQFClassGroupQuotientMorphism(G, H)
796+
sage: elt = G(BinaryQF(333, 306, 422))
797+
sage: proj(elt)
798+
Class of 9*x^2 + 4*x*y + 9*y^2
799+
sage: proj(elt) == H(_project_bqf(_project_bqf(elt.form(), 3), 13))
800+
True
801+
sage: proj(elt) == H(_project_bqf(_project_bqf(elt.form(), 13), 3))
802+
True
803+
804+
ALGORITHM: Repeated application of :func:`_project_bqf` for the prime factors in `f`.
805+
"""
806+
bqf = elt.form()
807+
for q,m in self.f:
808+
for _ in range(m):
809+
bqf = _project_bqf(bqf, q)
810+
return self.codomain()(bqf)

0 commit comments

Comments
 (0)