Skip to content

Commit c0a616a

Browse files
committed
implement analytic class number formula (product form) for approximating class numbers of negative fundamental discriminants
1 parent 83b52a7 commit c0a616a

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed

src/sage/rings/number_field/order.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,83 @@ def quadratic_order_class_number(disc):
117117
h = pari.qfbclassno(disc)
118118
return ZZ(h)
119119

120+
def quadratic_order_approximate_class_number(disc, *, bound=10**4):
121+
r"""
122+
Return *an approximation of* the class number of
123+
the quadratic order of given discriminant.
124+
125+
Currently only implemented for maximal orders
126+
in imaginary-quadratic fields.
127+
128+
EXAMPLES::
129+
130+
sage: from sage.rings.number_field.order import quadratic_order_approximate_class_number
131+
sage: QuadraticField(-419).class_number()
132+
9
133+
sage: quadratic_order_approximate_class_number(-419)
134+
9.0...
135+
136+
::
137+
138+
sage: from sage.rings.number_field.order import quadratic_order_approximate_class_number
139+
sage: d = 100000000000031
140+
sage: QuadraticField(-d).class_number(proof=False)
141+
14414435
142+
sage: round(quadratic_order_approximate_class_number(-d))
143+
144...
144+
sage: round(quadratic_order_approximate_class_number(-d, bound=10**6))
145+
1441...
146+
147+
::
148+
149+
sage: from sage.rings.number_field.order import quadratic_order_approximate_class_number
150+
sage: # Test it against the exact class number computed for the CSIDH-512 prime
151+
sage: # Source: https://eprint.iacr.org/2019/498.pdf
152+
sage: p = 4 * prod(primes(3,374)) * 587 - 1
153+
sage: hreal = 84884147409828091725676728670213067387206838101828807864190286991865870575397
154+
sage: assert not hreal * BQFClassGroup(-p).random_element()
155+
sage: h = round(quadratic_order_approximate_class_number(-p, bound=10**3)); h
156+
8...
157+
sage: RR(h / hreal)
158+
1.00...
159+
sage: h = round(quadratic_order_approximate_class_number(-p, bound=10**4)); h
160+
84...
161+
sage: RR(h / hreal)
162+
0.99...
163+
sage: h = round(quadratic_order_approximate_class_number(-p, bound=10**5)); h
164+
848...
165+
sage: RR(h / hreal)
166+
0.999...
167+
sage: h = round(quadratic_order_approximate_class_number(-p, bound=10**6)); h # long time -- 2s
168+
84884...
169+
sage: RR(h / hreal) # long time -- 2s
170+
1.00000...
171+
172+
ALGORITHM: Finite approximation of the infinite product given by
173+
the analytic class number formula, using primes up to ``bound``.
174+
"""
175+
disc = ZZ(disc)
176+
if disc >= 0:
177+
raise NotImplementedError('only imaginary-quadratic fields supported')
178+
if not disc.is_fundamental_discriminant():
179+
raise NotImplementedError('only fundamental discriminants supported')
180+
181+
from sage.rings.real_mpfr import RealField
182+
from sage.arith.misc import primes, kronecker_symbol
183+
from sage.symbolic.constants import pi
184+
185+
w = 6 if disc == -3 else 4 if disc == -4 else 2
186+
RR = RealField(max(53, disc.bit_length())) # wild guess!
187+
188+
# compute numerator and denominator separately for numerical stability
189+
L1 = L2 = RR(1)
190+
for ell in primes(bound):
191+
L1 *= ell
192+
L2 *= ell - kronecker_symbol(disc, ell)
193+
L = L1 / L2
194+
195+
return RR(w * abs(disc).sqrt() * L / (2 * pi))
196+
120197

121198
class OrderFactory(UniqueFactory):
122199
r"""

0 commit comments

Comments
 (0)