@@ -164,6 +164,9 @@ class constraints:
164
164
def negate (self ):
165
165
return constraints (zero = self .nonzero , nonzero = self .zero )
166
166
167
+ def map (self , fun ):
168
+ return constraints (zero = {fun (k ): v for k , v in self .zero .items ()}, nonzero = {fun (k ): v for k , v in self .nonzero .items ()})
169
+
167
170
def __add__ (self , other ):
168
171
zero = self .zero .copy ()
169
172
zero .update (other .zero )
@@ -177,6 +180,30 @@ class constraints:
177
180
def __repr__ (self ):
178
181
return "%s" % self
179
182
183
+ def normalize_factor (p ):
184
+ """Normalizes the sign of primitive polynomials (as returned by factor())
185
+
186
+ This function ensures that the polynomial has a positive leading coefficient.
187
+
188
+ This is necessary because recent sage versions (starting with v9.3 or v9.4,
189
+ we don't know) are inconsistent about the placement of the minus sign in
190
+ polynomial factorizations:
191
+ ```
192
+ sage: R.<ax,bx,ay,by,Az,Bz,Ai,Bi> = PolynomialRing(QQ,8,order='invlex')
193
+ sage: R((-2 * (bx - ax)) ^ 1).factor()
194
+ (-2) * (bx - ax)
195
+ sage: R((-2 * (bx - ax)) ^ 2).factor()
196
+ (4) * (-bx + ax)^2
197
+ sage: R((-2 * (bx - ax)) ^ 3).factor()
198
+ (8) * (-bx + ax)^3
199
+ ```
200
+ """
201
+ # Assert p is not 0 and that its non-zero coeffients are coprime.
202
+ # (We could just work with the primitive part p/p.content() but we want to be
203
+ # aware if factor() does not return a primitive part in future sage versions.)
204
+ assert p .content () == 1
205
+ # Ensure that the first non-zero coefficient is positive.
206
+ return p if p .lc () > 0 else - p
180
207
181
208
def conflicts (R , con ):
182
209
"""Check whether any of the passed non-zero assumptions is implied by the zero assumptions"""
@@ -204,10 +231,10 @@ def get_nonzero_set(R, assume):
204
231
nonzero = set ()
205
232
for nz in map (numerator , assume .nonzero ):
206
233
for (f ,n ) in nz .factor ():
207
- nonzero .add (f )
234
+ nonzero .add (normalize_factor ( f ) )
208
235
rnz = zero .reduce (nz )
209
236
for (f ,n ) in rnz .factor ():
210
- nonzero .add (f )
237
+ nonzero .add (normalize_factor ( f ) )
211
238
return nonzero
212
239
213
240
@@ -222,27 +249,27 @@ def prove_nonzero(R, exprs, assume):
222
249
return (False , [exprs [expr ]])
223
250
allexprs = reduce (lambda a ,b : numerator (a )* numerator (b ), exprs , 1 )
224
251
for (f , n ) in allexprs .factor ():
225
- if f not in nonzero :
252
+ if normalize_factor ( f ) not in nonzero :
226
253
ok = False
227
254
if ok :
228
255
return (True , None )
229
256
ok = True
230
- for (f , n ) in zero .reduce (numerator ( allexprs ) ).factor ():
231
- if f not in nonzero :
257
+ for (f , n ) in zero .reduce (allexprs ).factor ():
258
+ if normalize_factor ( f ) not in nonzero :
232
259
ok = False
233
260
if ok :
234
261
return (True , None )
235
262
ok = True
236
263
for expr in exprs :
237
264
for (f ,n ) in numerator (expr ).factor ():
238
- if f not in nonzero :
265
+ if normalize_factor ( f ) not in nonzero :
239
266
ok = False
240
267
if ok :
241
268
return (True , None )
242
269
ok = True
243
270
for expr in exprs :
244
271
for (f ,n ) in zero .reduce (numerator (expr )).factor ():
245
- if f not in nonzero :
272
+ if normalize_factor ( f ) not in nonzero :
246
273
expl .add (exprs [expr ])
247
274
if expl :
248
275
return (False , list (expl ))
@@ -254,7 +281,7 @@ def prove_zero(R, exprs, assume):
254
281
"""Check whether all of the passed expressions are provably zero, given assumptions"""
255
282
r , e = prove_nonzero (R , dict (map (lambda x : (fastfrac (R , x .bot , 1 ), exprs [x ]), exprs )), assume )
256
283
if not r :
257
- return (False , map (lambda x : "Possibly zero denominator: %s" % x , e ))
284
+ return (False , list ( map (lambda x : "Possibly zero denominator: %s" % x , e ) ))
258
285
zero = R .ideal (list (map (numerator , assume .zero )))
259
286
nonzero = prod (x for x in assume .nonzero )
260
287
expl = []
@@ -279,17 +306,17 @@ def describe_extra(R, assume, assumeExtra):
279
306
if base not in zero :
280
307
add = []
281
308
for (f , n ) in numerator (base ).factor ():
282
- if f not in nonzero :
283
- add += ["%s" % f ]
309
+ if normalize_factor ( f ) not in nonzero :
310
+ add += ["%s" % normalize_factor ( f ) ]
284
311
if add :
285
312
ret .add ((" * " .join (add )) + " = 0 [%s]" % assumeExtra .zero [base ])
286
313
# Iterate over the extra nonzero expressions
287
314
for nz in assumeExtra .nonzero :
288
315
nzr = zeroextra .reduce (numerator (nz ))
289
316
if nzr not in zeroextra :
290
317
for (f ,n ) in nzr .factor ():
291
- if zeroextra .reduce (f ) not in nonzero :
292
- ret .add ("%s != 0" % zeroextra .reduce (f ))
318
+ if normalize_factor ( zeroextra .reduce (f ) ) not in nonzero :
319
+ ret .add ("%s != 0" % normalize_factor ( zeroextra .reduce (f ) ))
293
320
return ", " .join (x for x in ret )
294
321
295
322
@@ -299,22 +326,21 @@ def check_symbolic(R, assumeLaw, assumeAssert, assumeBranch, require):
299
326
300
327
if conflicts (R , assume ):
301
328
# This formula does not apply
302
- return None
329
+ return ( True , None )
303
330
304
331
describe = describe_extra (R , assumeLaw + assumeBranch , assumeAssert )
332
+ if describe != "" :
333
+ describe = " (assuming " + describe + ")"
305
334
306
335
ok , msg = prove_zero (R , require .zero , assume )
307
336
if not ok :
308
- return "FAIL, %s fails (assuming %s) " % (str (msg ), describe )
337
+ return ( False , "FAIL, %s fails%s " % (str (msg ), describe ) )
309
338
310
339
res , expl = prove_nonzero (R , require .nonzero , assume )
311
340
if not res :
312
- return "FAIL, %s fails (assuming %s) " % (str (expl ), describe )
341
+ return ( False , "FAIL, %s fails%s " % (str (expl ), describe ) )
313
342
314
- if describe != "" :
315
- return "OK (assuming %s)" % describe
316
- else :
317
- return "OK"
343
+ return (True , "OK%s" % describe )
318
344
319
345
320
346
def concrete_verify (c ):
0 commit comments