Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 78
1
import sys
2
sys.path.append(".") # Needed to pass Sage's automated testing
3
4
from sage.all import *
5
6
7
class Conjecture(SageObject): #Based on GraphExpression from IndependenceNumberProject
8
9
def __init__(self, stack, expression, pickling):
10
"""Constructs a new Conjecture from the given stack of functions."""
11
self.stack = stack
12
self.expression = expression
13
self.pickling = pickling
14
self.__name__ = ''.join(c for c in repr(self.expression) if c != ' ')
15
super(Conjecture, self).__init__()
16
17
def __eq__(self, other):
18
return self.stack == other.stack and self.expression == other.expression
19
20
def __reduce__(self):
21
return (_makeConjecture, self.pickling)
22
23
def _repr_(self):
24
return repr(self.expression)
25
26
def _latex_(self):
27
return latex(self.expression)
28
29
def __call__(self, g, returnBoundValue=False):
30
return self.evaluate(g, returnBoundValue)
31
32
def evaluate(self, g, returnBoundValue=False):
33
stack = []
34
if returnBoundValue:
35
assert self.stack[-1][0] in {operator.le, operator.lt, operator.ge, operator.gt}, "Conjecture is not a bound"
36
for op, opType in self.stack:
37
if opType==0:
38
stack.append(op(g))
39
elif opType==1:
40
stack.append(op(stack.pop()))
41
elif opType==2:
42
right = stack.pop()
43
left = stack.pop()
44
if type(left) == sage.symbolic.expression.Expression:
45
left = left.n()
46
if type(right) == sage.symbolic.expression.Expression:
47
right = right.n()
48
if op == operator.truediv and right == 0:
49
if left > 0:
50
stack.append(float('inf'))
51
elif left < 0:
52
stack.append(float('-inf'))
53
else:
54
stack.append(float('NaN'))
55
elif op == operator.pow and (right == Infinity or right == float('inf')):
56
if left < -1 or left > 1:
57
stack.append(float('inf'))
58
elif -1 < left < 1:
59
stack.append(0)
60
else:
61
stack.append(1)
62
elif op == operator.pow and (right == -Infinity or right == float('-inf')):
63
if left < -1 or left > 1:
64
stack.append(0)
65
elif -1 < left < 1:
66
stack.append(float('inf'))
67
else:
68
stack.append(1)
69
elif op == operator.pow and left == 0 and right < 0:
70
stack.append(float('inf'))
71
elif op == operator.pow and left == -Infinity and right not in ZZ: #mimic C function pow
72
stack.append(float('inf'))
73
elif op == operator.pow and left < 0 and right not in ZZ: #mimic C function pow
74
stack.append(float('nan'))
75
elif op == operator.pow and right > 2147483647: #prevent RuntimeError
76
stack.append(float('nan'))
77
elif op in {operator.le, operator.lt, operator.ge, operator.gt}:
78
left = round(left, 6)
79
right = round(right, 6)
80
if returnBoundValue:
81
stack.append(right)
82
else:
83
stack.append(op(left, right))
84
else:
85
stack.append(op(left, right))
86
return stack.pop()
87
88
def wrapUnboundMethod(op, invariantsDict):
89
return lambda obj: getattr(obj, invariantsDict[op].__name__)()
90
91
def wrapBoundMethod(op, invariantsDict):
92
return lambda obj: invariantsDict[op](obj)
93
94
def _makeConjecture(inputList, variable, invariantsDict):
95
import operator
96
97
specials = {'-1', '+1', '*2', '/2', '^2', '-()', '1/', 'log10', 'max', 'min'}
98
99
unaryOperators = {'sqrt': sqrt, 'ln': log}
100
binaryOperators = {'+': operator.add, '*': operator.mul, '-': operator.sub, '/': operator.truediv, '^': operator.pow}
101
comparators = {'<': operator.lt, '<=': operator.le, '>': operator.gt, '>=': operator.ge}
102
expressionStack = []
103
operatorStack = []
104
105
for op in inputList:
106
if op in invariantsDict:
107
import types
108
if type(invariantsDict[op]) in (types.BuiltinMethodType, types.MethodType):
109
f = wrapUnboundMethod(op, invariantsDict)
110
else:
111
f = wrapBoundMethod(op, invariantsDict)
112
expressionStack.append(sage.symbolic.function_factory.function(op, variable))
113
operatorStack.append((f,0))
114
elif op in specials:
115
_handleSpecialOperators(expressionStack, op)
116
operatorStack.append(_getSpecialOperators(op))
117
elif op in unaryOperators:
118
expressionStack.append(unaryOperators[op](expressionStack.pop()))
119
operatorStack.append((unaryOperators[op],1))
120
elif op in binaryOperators:
121
right = expressionStack.pop()
122
left = expressionStack.pop()
123
expressionStack.append(binaryOperators[op](left, right))
124
operatorStack.append((binaryOperators[op],2))
125
elif op in comparators:
126
right = expressionStack.pop()
127
left = expressionStack.pop()
128
expressionStack.append(comparators[op](left, right))
129
operatorStack.append((comparators[op],2))
130
else:
131
raise ValueError("Error while reading output from expressions. Unknown element: {}".format(op))
132
133
return Conjecture(operatorStack, expressionStack.pop(), (inputList, variable, invariantsDict))
134
135
def _handleSpecialOperators(stack, op):
136
if op == '-1':
137
stack.append(stack.pop()-1)
138
elif op == '+1':
139
stack.append(stack.pop()+1)
140
elif op == '*2':
141
stack.append(stack.pop()*2)
142
elif op == '/2':
143
stack.append(stack.pop()/2)
144
elif op == '^2':
145
x = stack.pop()
146
stack.append(x*x)
147
elif op == '-()':
148
stack.append(-stack.pop())
149
elif op == '1/':
150
stack.append(1/stack.pop())
151
elif op == 'log10':
152
stack.append(log(stack.pop(),10))
153
elif op == 'max':
154
stack.append(function('maximum',stack.pop(),stack.pop()))
155
elif op == 'min':
156
stack.append(function('minimum',stack.pop(),stack.pop()))
157
else:
158
raise ValueError("Unknown operator: {}".format(op))
159
160
def _getSpecialOperators(op):
161
if op == '-1':
162
return (lambda x: x-1), 1
163
elif op == '+1':
164
return (lambda x: x+1), 1
165
elif op == '*2':
166
return (lambda x: x*2), 1
167
elif op == '/2':
168
return (lambda x: x*0.5), 1
169
elif op == '^2':
170
return (lambda x: x*x), 1
171
elif op == '-()':
172
return (lambda x: -x), 1
173
elif op == '1/':
174
return (lambda x: float('inf') if x==0 else 1.0/x), 1
175
elif op == 'log10':
176
return (lambda x: log(x,10)), 1
177
elif op == 'max':
178
return max, 2
179
elif op == 'min':
180
return min, 2
181
else:
182
raise ValueError("Unknown operator: {}".format(op))
183
184
def allOperators():
185
return { '-1', '+1', '*2', '/2', '^2', '-()', '1/', 'sqrt', 'ln', 'log10', '+', '*', 'max', 'min', '-', '/', '^'}
186
187
def conjecture(objects, invariants, mainInvariant, variableName='x', time=5, debug=False, verbose=False, upperBound=True,
188
operators=None, theory=None):
189
190
if len(invariants)<2 or len(objects)==0: return
191
if not theory: theory=None
192
193
assert 0 <= mainInvariant < len(invariants), 'Illegal value for mainInvariant'
194
195
operatorDict = { '-1' : 'U 0', '+1' : 'U 1', '*2' : 'U 2', '/2' : 'U 3', '^2' : 'U 4', '-()' : 'U 5', '1/' : 'U 6',
196
'sqrt' : 'U 7', 'ln' : 'U 8', 'log10' : 'U 9', '+' : 'C 0', '*' : 'C 1', 'max' : 'C 2', 'min' : 'C 3',
197
'-' : 'N 0', '/' : 'N 1', '^' : 'N 2'}
198
199
# check whether number of invariants and objects falls within the allowed bounds
200
import subprocess
201
sp = subprocess.Popen('./expressions --limits all', shell=True,
202
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
203
stderr=subprocess.PIPE, close_fds=True)
204
205
limits = {key:int(value) for key, value in (l.split(':') for l in sp.stdout)}
206
207
assert len(objects) <= limits['MAX_OBJECT_COUNT'], 'This version of expressions does not support that many objects.'
208
assert len(invariants) <= limits['MAX_INVARIANT_COUNT'], 'This version of expressions does not support that many invariants.'
209
210
# prepare the invariants to be used in conjecturing
211
invariantsDict = {}
212
names = []
213
214
for pos, invariant in enumerate(invariants):
215
if type(invariant) == tuple:
216
name, invariant = invariant
217
elif hasattr(invariant, '__name__'):
218
if invariant.__name__ in invariantsDict:
219
name = '{}_{}'.format(invariant.__name__, pos)
220
else:
221
name = invariant.__name__
222
else:
223
name = 'invariant_{}'.format(pos)
224
invariantsDict[name] = invariant
225
names.append(name)
226
227
# call the conjecturing program
228
command = './expressions -c{}{} --dalmatian {}--time {} --invariant-names --output stack {} --allowed-skips 0'
229
command = command.format('v' if verbose and debug else '', 't' if theory is not None else '',
230
'--all-operators ' if operators is None else '',
231
time, '--leq' if upperBound else '--geq')
232
233
sp = subprocess.Popen(command, shell=True,
234
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
235
stderr=subprocess.PIPE, close_fds=True)
236
stdin = sp.stdin
237
238
if operators is not None:
239
stdin.write('{}\n'.format(len(operators)))
240
for op in operators:
241
stdin.write('{}\n'.format(operatorDict[op]))
242
243
stdin.write('{} {} {}\n'.format(len(objects), len(invariants), mainInvariant + 1))
244
245
for invariant in names:
246
stdin.write('{}\n'.format(invariant))
247
248
if theory is not None:
249
for o in objects:
250
if upperBound:
251
try:
252
stdin.write('{}\n'.format(min(float(t(o)) for t in theory)))
253
except:
254
stdin.write('NaN\n')
255
else:
256
try:
257
stdin.write('{}\n'.format(max(float(t(o)) for t in theory)))
258
except:
259
stdin.write('NaN\n')
260
261
for o in objects:
262
for invariant in names:
263
try:
264
stdin.write('{}\n'.format(float(invariantsDict[invariant](o))))
265
except:
266
stdin.write('NaN\n')
267
268
if debug:
269
for l in sp.stderr:
270
print '> ' + l.rstrip()
271
272
# process the output
273
out = sp.stdout
274
275
variable = SR.var(variableName)
276
277
conjectures = []
278
inputList = []
279
280
for l in out:
281
op = l.strip()
282
if op:
283
inputList.append(op)
284
else:
285
conjectures.append(_makeConjecture(inputList, variable, invariantsDict))
286
inputList = []
287
288
return conjectures
289
290
class PropertyBasedConjecture(SageObject):
291
292
def __init__(self, expression, propertyCalculators, pickling):
293
"""Constructs a new Conjecture from the given stack of functions."""
294
self.expression = expression
295
self.propertyCalculators = propertyCalculators
296
self.pickling = pickling
297
self.__name__ = repr(self.expression)
298
super(PropertyBasedConjecture, self).__init__()
299
300
def __eq__(self, other):
301
return self.expression == other.expression
302
303
def __reduce__(self):
304
return (_makePropertyBasedConjecture, self.pickling)
305
306
def _repr_(self):
307
return repr(self.expression)
308
309
def _latex_(self):
310
return latex(self.expression)
311
312
def __call__(self, g):
313
return self.evaluate(g)
314
315
def evaluate(self, g):
316
values = {prop: f(g) for (prop, f) in self.propertyCalculators.items()}
317
return self.expression.evaluate(values)
318
319
def _makePropertyBasedConjecture(inputList, invariantsDict):
320
import operator
321
322
binaryOperators = {'&', '|', '^', '->'}
323
324
expressionStack = []
325
propertyCalculators = {}
326
327
for op in inputList:
328
if op in invariantsDict:
329
import types
330
if type(invariantsDict[op]) in (types.BuiltinMethodType, types.MethodType):
331
f = wrapUnboundMethod(op, invariantsDict)
332
else:
333
f = wrapBoundMethod(op, invariantsDict)
334
prop = ''.join([l for l in op if l.strip()])
335
expressionStack.append(prop)
336
propertyCalculators[prop] = f
337
elif op == '<-':
338
right = expressionStack.pop()
339
left = expressionStack.pop()
340
expressionStack.append('({})->({})'.format(right, left))
341
elif op == '~':
342
expressionStack.append('~({})'.format(expressionStack.pop()))
343
elif op in binaryOperators:
344
right = expressionStack.pop()
345
left = expressionStack.pop()
346
expressionStack.append('({}){}({})'.format(left, op, right))
347
else:
348
raise ValueError("Error while reading output from expressions. Unknown element: {}".format(op))
349
350
import sage.logic.propcalc as propcalc
351
return PropertyBasedConjecture(propcalc.formula(expressionStack.pop()), propertyCalculators, (inputList, invariantsDict))
352
353
def allPropertyBasedOperators():
354
return { '~', '&', '|', '^', '->'}
355
356
def propertyBasedConjecture(objects, invariants, mainInvariant, time=5, debug=False, verbose=False, sufficient=True,
357
operators=None, theory=None):
358
359
if len(invariants)<2 or len(objects)==0: return
360
if not theory: theory=None
361
362
assert 0 <= mainInvariant < len(invariants), 'Illegal value for mainInvariant'
363
364
operatorDict = { '~' : 'U 0', '&' : 'C 0', '|' : 'C 1', '^' : 'C 2', '->' : 'N 0'}
365
366
# check whether number of invariants and objects falls within the allowed bounds
367
import subprocess
368
sp = subprocess.Popen('./expressions --limits all', shell=True,
369
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
370
stderr=subprocess.PIPE, close_fds=True)
371
372
limits = {key:int(value) for key, value in (l.split(':') for l in sp.stdout)}
373
374
assert len(objects) <= limits['MAX_OBJECT_COUNT'], 'This version of expressions does not support that many objects.'
375
assert len(invariants) <= limits['MAX_INVARIANT_COUNT'], 'This version of expressions does not support that many invariants.'
376
377
# prepare the invariants to be used in conjecturing
378
invariantsDict = {}
379
names = []
380
381
for pos, invariant in enumerate(invariants):
382
if type(invariant) == tuple:
383
name, invariant = invariant
384
elif hasattr(invariant, '__name__'):
385
if invariant.__name__ in invariantsDict:
386
name = '{}_{}'.format(invariant.__name__, pos)
387
else:
388
name = invariant.__name__
389
else:
390
name = 'invariant_{}'.format(pos)
391
invariantsDict[name] = invariant
392
names.append(name)
393
394
# call the conjecturing program
395
command = './expressions -pc{}{} --dalmatian {}--time {} --invariant-names --output stack {} --allowed-skips 0'
396
command = command.format('v' if verbose and debug else '', 't' if theory is not None else '',
397
'--all-operators ' if operators is None else '',
398
time, '--sufficient' if sufficient else '--necessary')
399
400
if verbose:
401
print('Using the following command')
402
print(command)
403
404
sp = subprocess.Popen(command, shell=True,
405
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
406
stderr=subprocess.PIPE, close_fds=True)
407
stdin = sp.stdin
408
409
if operators is not None:
410
stdin.write('{}\n'.format(len(operators)))
411
for op in operators:
412
stdin.write('{}\n'.format(operatorDict[op]))
413
414
stdin.write('{} {} {}\n'.format(len(objects), len(invariants), mainInvariant + 1))
415
416
for invariant in names:
417
stdin.write('{}\n'.format(invariant))
418
419
if theory is not None:
420
for o in objects:
421
if sufficient:
422
try:
423
stdin.write('{}\n'.format(max((1 if bool(t(o)) else 0) for t in theory)))
424
except:
425
stdin.write('-1\n')
426
else:
427
try:
428
stdin.write('{}\n'.format(min((1 if bool(t(o)) else 0) for t in theory)))
429
except:
430
stdin.write('-1\n')
431
432
for o in objects:
433
for invariant in names:
434
try:
435
stdin.write('{}\n'.format(1 if bool(invariantsDict[invariant](o)) else 0))
436
except:
437
stdin.write('-1\n')
438
439
if debug:
440
for l in sp.stderr:
441
print '> ' + l.rstrip()
442
443
# process the output
444
out = sp.stdout
445
446
conjectures = []
447
inputList = []
448
449
for l in out:
450
op = l.strip()
451
if op:
452
inputList.append(op)
453
else:
454
conjectures.append(_makePropertyBasedConjecture(inputList, invariantsDict))
455
inputList = []
456
457
return conjectures
458
459