|
82 | 82 | from sage.structure.parent import Parent
|
83 | 83 | from sage.structure.unique_representation import UniqueRepresentation
|
84 | 84 | from sage.structure.element import AdditiveGroupElement
|
| 85 | +from sage.categories.morphism import Morphism |
85 | 86 |
|
86 | 87 | from sage.misc.prandom import randrange
|
87 | 88 | from sage.rings.integer_ring import ZZ
|
| 89 | +from sage.rings.finite_rings.integer_mod_ring import Zmod |
88 | 90 | from sage.rings.finite_rings.integer_mod import Mod
|
| 91 | +from sage.rings.polynomial.polynomial_ring import polygen |
89 | 92 | from sage.arith.misc import random_prime
|
| 93 | +from sage.matrix.constructor import matrix |
90 | 94 | from sage.groups.generic import order_from_multiple, multiple
|
91 | 95 | from sage.groups.additive_abelian.additive_abelian_wrapper import AdditiveAbelianGroupWrapper
|
92 | 96 | from sage.quadratic_forms.binary_qf import BinaryQF
|
@@ -330,6 +334,34 @@ def gens(self):
|
330 | 334 | """
|
331 | 335 | return [g.element() for g in self.abelian_group().gens()]
|
332 | 336 |
|
| 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 | + |
333 | 365 |
|
334 | 366 | class BQFClassGroup_element(AdditiveGroupElement):
|
335 | 367 | r"""
|
@@ -376,7 +408,7 @@ def __init__(self, F, parent, *, check=True, reduce=True):
|
376 | 408 | if not F.is_primitive():
|
377 | 409 | raise ValueError('given quadratic form is not primitive')
|
378 | 410 | 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') |
380 | 412 | if reduce:
|
381 | 413 | F = F.reduced_form()
|
382 | 414 | self._form = F
|
@@ -611,3 +643,168 @@ def order(self):
|
611 | 643 | 2
|
612 | 644 | """
|
613 | 645 | 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