Skip to content

Commit 5c90b60

Browse files
author
Release Manager
committed
gh-38680: Improve counting of local solutions for QuadraticForm at p=2 Fixes #38679. Introduces new method `CountAllLocalGoodTypesNormalForm(Q, p, k, m, zvec, nzvec)` to compute the number of local solutions of 'good type' for Q[v] = m mod p^k when Q is in local normal form at p. This replaces the use of the slow brute-force function `CountAllLocalTypesNaive` indirectly used in `local_good_density_congruence_even` for (p,k)=(2,3) . ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [ ] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies URL: #38680 Reported by: WvanWoerden Reviewer(s): Frédéric Chapoton, Sebastian A. Spindler
2 parents 5c25c12 + b945f4b commit 5c90b60

File tree

2 files changed

+100
-25
lines changed

2 files changed

+100
-25
lines changed

src/sage/quadratic_forms/count_local_2.pyx

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@ def count_modp__by_gauss_sum(n, p, m, Qdet):
7272
neg1 = -1
7373
if not m % p:
7474
if n % 2:
75-
count = p**(n-1)
75+
count = p**(n - 1)
7676
else:
77-
count = p**(n-1) + (p-1) * (p**((n-2)//2)) * kronecker_symbol(((neg1**(n//2)) * Qdet) % p, p)
77+
count = p**(n - 1) + (p - 1) * (p**((n - 2) // 2)) * kronecker_symbol(((neg1**(n // 2)) * Qdet) % p, p)
7878
else:
7979
if n % 2:
80-
count = p**(n-1) + p**((n-1)//2) * kronecker_symbol(((neg1**((n-1)//2)) * Qdet * m) % p, p)
80+
count = p**(n - 1) + p**((n - 1) // 2) * kronecker_symbol(((neg1**((n - 1) // 2)) * Qdet * m) % p, p)
8181
else:
82-
count = p**(n-1) - p**((n-2)//2) * kronecker_symbol(((neg1**(n//2)) * Qdet) % p, p)
82+
count = p**(n - 1) - p**((n - 2) // 2) * kronecker_symbol(((neg1**(n // 2)) * Qdet) % p, p)
8383

8484
# Return the result
8585
return count
@@ -115,13 +115,13 @@ cdef CountAllLocalTypesNaive_cdef(Q, p, k, m, zvec, nzvec):
115115

116116
# Perform a carry (when value = R-1) until we can increment freely
117117
ptr = len(v)
118-
while ((ptr > 0) and (v[ptr-1] == R-1)):
119-
v[ptr-1] += 1
118+
while ((ptr > 0) and (v[ptr - 1] == R - 1)):
119+
v[ptr - 1] += 1
120120
ptr += -1
121121

122122
# Only increment if we're not already at the zero vector =)
123123
if ptr > 0:
124-
v[ptr-1] += 1
124+
v[ptr - 1] += 1
125125

126126
# Evaluate Q(v) quickly
127127
tmp_val = Mod(0, R)
@@ -239,7 +239,7 @@ cdef local_solution_type_cdef(Q, p, w, zvec, nzvec):
239239
return <long> 1
240240
if p == 2:
241241
for i in range(n - 1):
242-
if Q[i, i+1] % p and (w[i] % p or w[i+1] % p):
242+
if Q[i, i + 1] % p and (w[i] % p or w[i + 1] % p):
243243
return <long> 1
244244

245245
# 2: Check Zero-type
@@ -257,11 +257,11 @@ cdef local_solution_type_cdef(Q, p, w, zvec, nzvec):
257257
# Compute the valuation of each index, allowing for off-diagonal terms
258258
if Q[i, i] == 0:
259259
if i == 0:
260-
val = valuation(Q[i, i+1], p) # Look at the term to the right
260+
val = valuation(Q[i, i + 1], p) # Look at the term to the right
261261
elif i == n - 1:
262-
val = valuation(Q[i-1, i], p) # Look at the term above
262+
val = valuation(Q[i - 1, i], p) # Look at the term above
263263
else:
264-
val = valuation(Q[i, i+1] + Q[i-1, i], p) # Finds the valuation of the off-diagonal term since only one isn't zero
264+
val = valuation(Q[i, i + 1] + Q[i - 1, i], p) # Finds the valuation of the off-diagonal term since only one isn't zero
265265
else:
266266
val = valuation(Q[i, i], p)
267267

@@ -281,3 +281,83 @@ cdef local_solution_type_cdef(Q, p, w, zvec, nzvec):
281281
print(" Solution vector is " + str(w))
282282
print(" and Q is \n" + str(Q) + "\n")
283283
raise RuntimeError("Error in IsLocalSolutionType: Should not execute this line... =( \n")
284+
285+
286+
def count_all_local_good_types_normal_form(Q, p, k, m, zvec, nzvec):
287+
r"""
288+
This is an internal routine, which is called by
289+
:meth:`sage.quadratic_forms.quadratic_form.QuadraticForm.local_good_density_congruence_even
290+
QuadraticForm.local_good_density_congruence_even`. See the documentation of
291+
that method for more details.
292+
293+
INPUT:
294+
295+
- ``Q`` -- quadratic form over `\ZZ` in local normal form at p with no zero blocks mod `p^k`
296+
- ``p`` -- prime number > 0
297+
- ``k`` -- integer > 0
298+
- ``m`` -- non-negative integer (depending only on mod `p^k`)
299+
- ``zvec``, ``nzvec`` -- list of integers in ``range(Q.dim())``, or ``None``
300+
301+
OUTPUT:
302+
303+
a non-negative integer giving the number of solutions of Good type.
304+
305+
EXAMPLES::
306+
307+
sage: from sage.quadratic_forms.count_local_2 import count_all_local_good_types_normal_form
308+
sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3])
309+
sage: Q_local_at2 = Q.local_normal_form(2)
310+
sage: Q_local_at3 = Q.local_normal_form(3)
311+
sage: count_all_local_good_types_normal_form(Q_local_at2, 2, 3, 3, None, None)
312+
64
313+
sage: count_all_local_good_types_normal_form(Q_local_at2, 2, 3, 3, [0], None)
314+
32
315+
sage: count_all_local_good_types_normal_form(Q_local_at3, 3, 2, 1, None, None)
316+
54
317+
"""
318+
n = Q.dim()
319+
if n == 0:
320+
return 0
321+
322+
m_range = p**k
323+
if zvec is None:
324+
zvec = []
325+
if nzvec is None:
326+
nzvec = []
327+
328+
# determine local blocks
329+
blocks = []
330+
i = 0
331+
while i < n - 1:
332+
if Q[i, i + 1] != 0:
333+
blocks += [(i, i + 1)]
334+
i += 2
335+
else:
336+
blocks += [(i,)]
337+
i += 1
338+
if i < n:
339+
blocks += [(i,)]
340+
341+
solutions = [[0, 0] for _ in range(m_range)] # [good, not good]
342+
solutions[0][1] = 1
343+
for b in blocks:
344+
Q_part = Q.extract_variables(b)
345+
zvec_local = range(len(b)) if (b[0] in zvec) else None
346+
nzvec_local = range(len(b)) if (b[0] in nzvec) else None
347+
348+
solutions_part = [[0, 0] for _ in range(m_range)]
349+
for m_part in range(m_range):
350+
cnt = CountAllLocalTypesNaive(Q_part, p, k, m_part, zvec_local, nzvec_local)
351+
solutions_part[m_part][0] = cnt[1]
352+
solutions_part[m_part][1] = cnt[0] - cnt[1]
353+
354+
# compute convolution of counts
355+
solutions_new = [[0, 0] for _ in range(m_range)]
356+
for m1 in range(m_range):
357+
for m2 in range(m_range):
358+
total = (solutions[m1][0] + solutions[m1][1]) * (solutions_part[m2][0] + solutions_part[m2][1])
359+
good = total - solutions[m1][1] * solutions_part[m2][1]
360+
solutions_new[(m1 + m2) % m_range][0] += good
361+
solutions_new[(m1 + m2) % m_range][1] += total - good
362+
solutions = solutions_new
363+
return solutions[m % m_range][0]

src/sage/quadratic_forms/quadratic_form__local_density_congruence.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from sage.arith.misc import valuation
1414
from sage.misc.verbose import verbose
1515

16-
from sage.quadratic_forms.count_local_2 import count_modp__by_gauss_sum
16+
from sage.quadratic_forms.count_local_2 import count_modp__by_gauss_sum, count_all_local_good_types_normal_form
1717

1818

1919
def count_modp_solutions__by_Gauss_sum(self, p, m):
@@ -210,17 +210,12 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec):
210210
[ * * * 20 ]
211211
sage: Q.theta_series(20) # needs sage.libs.pari
212212
1 + 2*q^5 + 2*q^10 + 2*q^14 + 2*q^15 + 2*q^16 + 2*q^18 + O(q^20)
213-
sage: Q.local_normal_form(2) # needs sage.libs.pari sage.rings.padics
214-
Quadratic form in 4 variables over Integer Ring with coefficients:
215-
[ 0 1 0 0 ]
216-
[ * 0 0 0 ]
217-
[ * * 0 1 ]
218-
[ * * * 0 ]
219-
sage: Q.local_good_density_congruence_even(1, None, None)
213+
sage: Q_local = Q.local_normal_form(2) # needs sage.libs.pari sage.rings.padics
214+
sage: Q_local.local_good_density_congruence_even(1, None, None) # needs sage.libs.pari sage.rings.padics
220215
3/4
221-
sage: Q.local_good_density_congruence_even(2, None, None)
222-
1
223-
sage: Q.local_good_density_congruence_even(5, None, None)
216+
sage: Q_local.local_good_density_congruence_even(2, None, None) # needs sage.libs.pari sage.rings.padics
217+
9/8
218+
sage: Q_local.local_good_density_congruence_even(5, None, None) # needs sage.libs.pari sage.rings.padics
224219
3/4
225220
"""
226221
n = self.dim()
@@ -296,7 +291,7 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec):
296291
# Take cases on the existence of additional nonzero congruence conditions (mod 2)
297292
if NZvec is None:
298293
total = (4 ** len(Z_Is8)) * (8 ** len(Is8_minus_Z)) \
299-
* Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(Z_Not8), None)
294+
* count_all_local_good_types_normal_form(Q_Not8,2, 3, m, list(Z_Not8), None)
300295
else:
301296
ZNZ = Z + Set(NZvec)
302297
ZNZ_Not8 = Not8.intersection(ZNZ)
@@ -310,9 +305,9 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec):
310305
verbose("Is8_minus_ZNZ = " + str(Is8_minus_ZNZ))
311306

312307
total = (4 ** len(Z_Is8)) * (8 ** len(Is8_minus_Z)) \
313-
* Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(Z_Not8), None) \
308+
* count_all_local_good_types_normal_form(Q_Not8, 2, 3, m, list(Z_Not8), None) \
314309
- (4 ** len(ZNZ_Is8)) * (8 ** len(Is8_minus_ZNZ)) \
315-
* Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(ZNZ_Not8), None)
310+
* count_all_local_good_types_normal_form(Q_Not8, 2, 3, m, list(ZNZ_Not8), None)
316311

317312
# DIAGNOSTIC
318313
verbose("total = " + str(total))

0 commit comments

Comments
 (0)