Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/quadratic_forms/quadratic_form__neighbors.py
4101 views
1
"""
2
Neighbors
3
"""
4
from sage.modules.free_module_element import vector
5
from sage.rings.integer_ring import ZZ
6
from copy import deepcopy
7
from sage.quadratic_forms.extras import extend_to_primitive
8
from sage.matrix.constructor import matrix
9
#from sage.quadratic_forms.quadratic_form import QuadraticForm ## This creates a circular import! =(
10
11
12
####################################################################################
13
## Routines used for understanding p-neighbors, and computing classes in a genus. ##
14
####################################################################################
15
16
17
def find_primitive_p_divisible_vector__random(self, p):
18
"""
19
Finds a random `p`-primitive vector in `L/pL` whose value is `p`-divisible.
20
21
.. note::
22
23
Since there are about `p^{(n-2)}` of these lines, we have a `1/p`
24
chance of randomly finding an appropriate vector.
25
26
.. warning::
27
28
If there are local obstructions for this to happen, then this algorithm
29
will never terminate... =( We should check for this too!
30
31
EXAMPLES::
32
33
sage: Q = QuadraticForm(ZZ, 2, [10,1,4])
34
sage: Q.find_primitive_p_divisible_vector__random(5) # random
35
(1, 1)
36
sage: Q.find_primitive_p_divisible_vector__random(5) # random
37
(1, 0)
38
sage: Q.find_primitive_p_divisible_vector__random(5) # random
39
(2, 0)
40
sage: Q.find_primitive_p_divisible_vector__random(5) # random
41
(2, 2)
42
sage: Q.find_primitive_p_divisible_vector__random(5) # random
43
(3, 3)
44
sage: Q.find_primitive_p_divisible_vector__random(5) # random
45
(3, 3)
46
sage: Q.find_primitive_p_divisible_vector__random(5) # random
47
(2, 0)
48
49
"""
50
n = self.dim()
51
v = vector([ZZ.random_element(p) for i in range(n)])
52
53
## Repeatedly choose random vectors, and evaluate until the value is p-divisible.
54
while True:
55
if (self(v) % p == 0) and (v != 0):
56
return v
57
else:
58
v[ZZ.random_element(n)] = ZZ.random_element(p) ## Replace a random entry and try again.
59
60
61
62
63
#def find_primitive_p_divisible_vector__all(self, p):
64
# """
65
# Finds all random p-primitive vectors (up to scaling) in L/pL whose
66
# value is p-divisible.
67
#
68
# Note: Since there are about p^(n-2) of these lines, we should avoid this for large n.
69
# """
70
# pass
71
72
73
def find_primitive_p_divisible_vector__next(self, p, v=None):
74
"""
75
Finds the next `p`-primitive vector (up to scaling) in `L/pL` whose
76
value is `p`-divisible, where the last vector returned was `v`. For
77
an intial call, no `v` needs to be passed.
78
79
Returns vectors whose last non-zero entry is normalized to 0 or 1 (so no
80
lines are counted repeatedly). The ordering is by increasing the
81
first non-normalized entry. If we have tested all (lines of)
82
vectors, then return None.
83
84
OUTPUT:
85
vector or None
86
87
EXAMPLES::
88
89
sage: Q = QuadraticForm(ZZ, 2, [10,1,4])
90
sage: v = Q.find_primitive_p_divisible_vector__next(5); v
91
(1, 1)
92
sage: v = Q.find_primitive_p_divisible_vector__next(5, v); v
93
(1, 0)
94
sage: v = Q.find_primitive_p_divisible_vector__next(5, v); v
95
96
97
"""
98
## Initialize
99
n = self.dim()
100
if v == None:
101
w = vector([ZZ(0) for i in range(n-1)] + [ZZ(1)])
102
else:
103
w = deepcopy(v)
104
105
106
## Handle n = 1 separately.
107
if n <= 1:
108
raise RuntimeError, "Sorry -- Not implemented yet!"
109
110
111
## Look for the last non-zero entry (which must be 1)
112
nz = n - 1
113
while w[nz] == 0:
114
nz += -1
115
116
## Test that the last non-zero entry is 1 (to detect tampering).
117
if w[nz] != 1:
118
print "Warning: The input vector to QuadraticForm.find_primitive_p_divisible_vector__next() is not normalized properly."
119
120
121
122
## Look for the next vector, until w == 0
123
while True:
124
125
126
## Look for the first non-maximal (non-normalized) entry
127
ind = 0
128
while (ind < nz) and (w[ind] == p-1):
129
ind += 1
130
131
#print ind, nz, w
132
133
## Increment
134
if (ind < nz):
135
w[ind] += 1
136
for j in range(ind):
137
w[j] = 0
138
else:
139
for j in range(ind+1): ## Clear all entries
140
w[j] = 0
141
142
if nz != 0: ## Move the non-zero normalized index over by one, or return the zero vector
143
w[nz-1] = 1
144
nz += -1
145
146
147
## Test for zero vector
148
if w == 0:
149
return None
150
151
## Test for p-divisibility
152
if (self(w) % p == 0):
153
return w
154
155
156
157
158
## ----------------------------------------------------------------------------------------------
159
160
def find_p_neighbor_from_vec(self, p, v):
161
"""
162
Finds the `p`-neighbor of this quadratic form associated to a given
163
vector `v` satisfying:
164
165
#. `Q(v) = 0 \pmod p`
166
#. `v` is a non-singular point of the conic `Q(v) = 0 \pmod p`.
167
168
Reference: Gonzalo Tornaria's Thesis, Thrm 3.5, p34.
169
170
EXAMPLES::
171
172
sage: Q = DiagonalQuadraticForm(ZZ,[1,1,1,1])
173
sage: v = vector([0,2,1,1])
174
sage: X = Q.find_p_neighbor_from_vec(3,v); X
175
Quadratic form in 4 variables over Integer Ring with coefficients:
176
[ 3 10 0 -4 ]
177
[ * 9 0 -6 ]
178
[ * * 1 0 ]
179
[ * * * 2 ]
180
181
"""
182
R = self.base_ring()
183
n = self.dim()
184
B2 = self.matrix()
185
186
## Find a (dual) vector w with B(v,w) != 0 (mod p)
187
v_dual = B2 * vector(v) ## We want the dot product with this to not be divisible by 2*p.
188
y_ind = 0
189
while ((y_ind < n) and (v_dual[y_ind] % p) == 0): ## Check the dot product for the std basis vectors!
190
y_ind += 1
191
if y_ind == n:
192
raise RuntimeError, "Oops! One of the standard basis vectors should have worked."
193
w = vector([R(i == y_ind) for i in range(n)])
194
vw_prod = (v * self.matrix()).dot_product(w)
195
196
## DIAGNOSTIC
197
#if vw_prod == 0:
198
# print "v = ", v
199
# print "v_dual = ", v_dual
200
# print "v_dual[y_ind] = ", v_dual[y_ind]
201
# print "(v_dual[y_ind] % p) = ", (v_dual[y_ind] % p)
202
# print "w = ", w
203
# print "p = ", p
204
# print "vw_prod = ", vw_prod
205
# raise RuntimeError, "ERROR: Why is vw_prod = 0?"
206
207
## DIAGNOSTIC
208
#print "v = ", v
209
#print "w = ", w
210
#print "vw_prod = ", vw_prod
211
212
213
## Lift the vector v to a vector v1 s.t. Q(v1) = 0 (mod p^2)
214
s = self(v)
215
if (s % p**2 != 0):
216
al = (-s / (p * vw_prod)) % p
217
v1 = v + p * al * w
218
v1w_prod = (v1 * self.matrix()).dot_product(w)
219
else:
220
v1 = v
221
v1w_prod = vw_prod
222
223
## DIAGNOSTIC
224
#if (s % p**2 != 0):
225
# print "al = ", al
226
#print "v1 = ", v1
227
#print "v1w_prod = ", v1w_prod
228
229
230
## Construct a special p-divisible basis to use for the p-neighbor switch
231
good_basis = extend_to_primitive([v1, w])
232
for i in range(2,n):
233
ith_prod = (good_basis[i] * self.matrix()).dot_product(v)
234
c = (ith_prod / v1w_prod) % p
235
good_basis[i] = good_basis[i] - c * w ## Ensures that this extension has <v_i, v> = 0 (mod p)
236
237
## DIAGNOSTIC
238
#print "original good_basis = ", good_basis
239
240
## Perform the p-neighbor switch
241
good_basis[0] = vector([x/p for x in good_basis[0]]) ## Divide v1 by p
242
good_basis[1] = good_basis[1] * p ## Multiply w by p
243
244
## Return the associated quadratic form
245
M = matrix(good_basis)
246
new_Q = deepcopy(self) ## Note: This avoids a circular import of QuadraticForm!
247
new_Q.__init__(R, M * self.matrix() * M.transpose())
248
return new_Q
249
return QuadraticForm(R, M * self.matrix() * M.transpose())
250
251
252
## ----------------------------------------------------------------------------------------------
253
254
255
#def find_classes_in_genus(self):
256
257
258
259