Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
90 views
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', '10^'}
98
99
unaryOperators = {'sqrt': sqrt, 'ln': log, 'exp': exp, 'ceil': ceil, 'floor': floor,
100
'abs': abs, 'sin': sin, 'cos': cos, 'tan': tan, 'asin': arcsin,
101
'acos': arccos, 'atan': arctan, 'sinh': sinh, 'cosh': cosh,
102
'tanh': tanh, 'asinh': arcsinh, 'acosh': arccosh, 'atanh': arctanh}
103
binaryOperators = {'+': operator.add, '*': operator.mul, '-': operator.sub, '/': operator.truediv, '^': operator.pow}
104
comparators = {'<': operator.lt, '<=': operator.le, '>': operator.gt, '>=': operator.ge}
105
expressionStack = []
106
operatorStack = []
107
108
for op in inputList:
109
if op in invariantsDict:
110
import types
111
if type(invariantsDict[op]) in (types.BuiltinMethodType, types.MethodType):
112
f = wrapUnboundMethod(op, invariantsDict)
113
else:
114
f = wrapBoundMethod(op, invariantsDict)
115
expressionStack.append(sage.symbolic.function_factory.function(op)(variable))
116
operatorStack.append((f,0))
117
elif op in specials:
118
_handleSpecialOperators(expressionStack, op)
119
operatorStack.append(_getSpecialOperators(op))
120
elif op in unaryOperators:
121
expressionStack.append(unaryOperators[op](expressionStack.pop()))
122
operatorStack.append((unaryOperators[op],1))
123
elif op in binaryOperators:
124
right = expressionStack.pop()
125
left = expressionStack.pop()
126
expressionStack.append(binaryOperators[op](left, right))
127
operatorStack.append((binaryOperators[op],2))
128
elif op in comparators:
129
right = expressionStack.pop()
130
left = expressionStack.pop()
131
expressionStack.append(comparators[op](left, right))
132
operatorStack.append((comparators[op],2))
133
else:
134
raise ValueError("Error while reading output from expressions. Unknown element: {}".format(op))
135
136
return Conjecture(operatorStack, expressionStack.pop(), (inputList, variable, invariantsDict))
137
138
def _handleSpecialOperators(stack, op):
139
if op == '-1':
140
stack.append(stack.pop()-1)
141
elif op == '+1':
142
stack.append(stack.pop()+1)
143
elif op == '*2':
144
stack.append(stack.pop()*2)
145
elif op == '/2':
146
stack.append(stack.pop()/2)
147
elif op == '^2':
148
x = stack.pop()
149
stack.append(x*x)
150
elif op == '-()':
151
stack.append(-stack.pop())
152
elif op == '1/':
153
stack.append(1/stack.pop())
154
elif op == 'log10':
155
stack.append(log(stack.pop(),10))
156
elif op == 'max':
157
stack.append(function('maximum')(stack.pop(),stack.pop()))
158
elif op == 'min':
159
stack.append(function('minimum')(stack.pop(),stack.pop()))
160
elif op == '10^':
161
stack.append(10**stack.pop())
162
else:
163
raise ValueError("Unknown operator: {}".format(op))
164
165
def _getSpecialOperators(op):
166
if op == '-1':
167
return (lambda x: x-1), 1
168
elif op == '+1':
169
return (lambda x: x+1), 1
170
elif op == '*2':
171
return (lambda x: x*2), 1
172
elif op == '/2':
173
return (lambda x: x*0.5), 1
174
elif op == '^2':
175
return (lambda x: x*x), 1
176
elif op == '-()':
177
return (lambda x: -x), 1
178
elif op == '1/':
179
return (lambda x: float('inf') if x==0 else 1.0/x), 1
180
elif op == 'log10':
181
return (lambda x: log(x,10)), 1
182
elif op == 'max':
183
return max, 2
184
elif op == 'min':
185
return min, 2
186
elif op == '10^':
187
return (lambda x: 10**x), 1
188
else:
189
raise ValueError("Unknown operator: {}".format(op))
190
191
def allOperators():
192
"""
193
Returns a set containing all the operators that can be used with the
194
invariant-based conjecture method. This method can be used to quickly
195
get a set from which to remove some operators or to just get an idea
196
of how to write some operators.
197
198
There are at the moment 34 operators available, including, e.g., addition::
199
200
>>> len(allOperators())
201
34
202
>>> '+' in allOperators()
203
True
204
"""
205
return { '-1', '+1', '*2', '/2', '^2', '-()', '1/', 'sqrt', 'ln', 'log10',
206
'exp', '10^', 'ceil', 'floor', 'abs', 'sin', 'cos', 'tan', 'asin',
207
'acos', 'atan', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh',
208
'+', '*', 'max', 'min', '-', '/', '^'}
209
210
def conjecture(objects, invariants, mainInvariant, variableName='x', time=5,
211
debug=False, verbose=False, upperBound=True, operators=None,
212
theory=None, precomputed=None):
213
"""
214
Runs the conjecturing program for invariants with the provided objects,
215
invariants and main invariant. This method requires the program ``expressions``
216
to be in the current working directory of Sage.
217
218
INPUT:
219
220
- ``objects`` - a list of objects about which conjectures should be made.
221
- ``invariants`` - a list of functions (callable objects) which take a
222
single argument and return a numerical real value. Each function should
223
be able to produce a value for each of the elements of objects.
224
- ``mainInvariant`` - an integer that is the index of one of the elements
225
of invariants. All conjectures will then be a bound for the invariant that
226
corresponds to this index.
227
- ``upperBound`` - if given, this boolean value specifies whether upper or
228
lower bounds for the main invariant should be generated. If ``True``,
229
then upper bounds are generated. If ``False``, then lower bounds are
230
generated. The default value is ``True``
231
- ``time`` - if given, this integer specifies the number of seconds before
232
the conjecturing program times out and returns the best conjectures it
233
has at that point. The default value is 5.
234
- ``theory`` - if given, specifies a list of known bounds. If this is
235
``None``, then no known bounds are used. Otherwise each conjecture will
236
have to be more significant than the bounds in this list. This implies
237
that if each object obtains equality for any of the bounds in this list,
238
then no conjectures will be made. The default value is ``None``.
239
- ``precomputed`` - if given, specifies a way to obtain precomputed invariant
240
values for (some of) the objects. If this is ``None``, then no precomputed
241
values are used. If this is a tuple, then it has to have length 3. The
242
first element is then a dictionary, the second element is a function that
243
returns a key for an object, and the third element is a function that
244
returns a key for an invariant. When an invariant value for an object
245
needs to be computed it will first be checked whether the key of the
246
object is in the dictionary. If it is, then it should be associated with
247
a dictionary and it will be checked whether the key of the invariant is
248
in that dictionary. If it is, then the associated value will be taken as
249
the invariant value, otherwise the invariant value will be computed. If
250
``precomputed`` is not a tuple, it is assumed to be a dictionary, and the
251
same procedure as above is used, but the identity is used for both key
252
functions.
253
- ``operators`` - if given, specifies a set of operators that can be used.
254
If this is ``None``, then all known operators are used. Otherwise only
255
the specified operators are used. It is advised to use the method
256
``allOperators()`` to get a set containing all operators and then
257
removing the operators which are not needed. The default value is
258
``None``.
259
- ``variableName`` - if given, this name will be used to denote the objects
260
in the produced conjectures. The default value is ``'x'``. This option
261
only has a cosmetical purpose.
262
- ``debug`` - if given, this boolean value specifies whether the output of
263
the program ``expressions`` to ``stderr`` is printed. The default value
264
is ``False``.
265
- ``verbose`` - if given, this boolean value specifies whether the program
266
``expressions`` is ran in verbose mode. Note that this has nu purpose if
267
``debug`` is not also set to ``True``. The default value is ``False``.
268
269
EXAMPLES::
270
271
A very simple example defines just two functions that take an integer and
272
return an integer, and then generates conjectures for these invariant Using
273
the single integer 1. As we are specifying the index of the main invariant
274
to be 0, all conjectures will be upper bounds for ``a``::
275
276
>>> def a(n): return n
277
>>> def b(n): return n + 1
278
>>> conjecture([1], [a,b], 0)
279
[a(x) <= b(x) - 1]
280
281
We can also generate lower bound conjectures::
282
283
>>> conjecture([1], [a,b], 0, upperBound=False)
284
[a(x) >= b(x) - 1]
285
286
In order to get more nicely printed conjectures, we can change the default
287
variable name which is used in the conjectures::
288
289
>>> conjecture([1], [a,b], 0, variableName='n')
290
[a(n) <= b(n) - 1]
291
292
Conjectures can be made for any kind of object::
293
294
>>> def max_degree(g): return max(g.degree())
295
>>> objects = [graphs.CompleteGraph(i) for i in range(3,6)]
296
>>> invariants = [Graph.size, Graph.order, max_degree]
297
>>> mainInvariant = invariants.index(Graph.size)
298
>>> conjecture(objects, invariants, mainInvariant, variableName='G')
299
[size(G) <= 2*order(G),
300
size(G) <= max_degree(G)^2 - 1,
301
size(G) <= 1/2*max_degree(G)*order(G)]
302
303
In some cases there might be invariants that are slow to calculate for some
304
objects. For these cases, the method ``conjecture`` provides a way to specify
305
precomputed values for some objects::
306
307
>>> o_key = lambda g: g.canonical_label().graph6_string()
308
>>> i_key = lambda f: f.__name__
309
>>> objects = [graphs.CompleteGraph(3), graphs.SchlaefliGraph()]
310
>>> invariants = [Graph.chromatic_number, Graph.order]
311
>>> main_invariant = invariants.index(Graph.chromatic_number)
312
>>> precomputed = {o_key(graphs.SchlaefliGraph()) : {i_key(Graph.chromatic_number) : 9}}
313
>>> conjecture(objects, invariants, main_invariant, precomputed=(precomputed, o_key, i_key))
314
[chromatic_number(x) <= order(x), chromatic_number(x) <= 10^tanh(order(x)) - 1]
315
316
In some cases strange conjectures might be produced or one conjecture you
317
might be expecting does not show up. In this case you can use the ``debug``
318
and ``verbose`` option to find out what is going on behind the scene. By
319
enabling ``debug`` the program prints the reason it stopped generating
320
conjectures (time limit, no better conjectures possible, ...) and gives some
321
statistics about the number of conjectures it looked at::
322
323
>>> conjecture([1], [a,b], 0, debug=True)
324
> Generation process was stopped by the conjecturing heuristic.
325
> Found 2 unlabeled trees.
326
> Found 2 labeled trees.
327
> Found 2 valid expressions.
328
[a(x) <= b(x) - 1]
329
330
By also enabling ``verbose``, you can discover which values are actually
331
given to the program::
332
333
>>> conjecture([1], [a,b], 0, debug=True, verbose=True)
334
> Invariant 1 Invariant 2
335
> 1) 1.000000 2.000000
336
> Generating trees with 0 unary nodes and 0 binary nodes.
337
> Saving expression
338
> a <= b
339
> Status: 1 unlabeled tree, 1 labeled tree, 1 expression
340
> Generating trees with 1 unary node and 0 binary nodes.
341
> Conjecture is more significant for object 1.
342
> 2.000000 vs. 1.000000
343
> Saving expression
344
> a <= (b) - 1
345
> Status: 2 unlabeled trees, 2 labeled trees, 2 expressions
346
> Generation process was stopped by the conjecturing heuristic.
347
> Found 2 unlabeled trees.
348
> Found 2 labeled trees.
349
> Found 2 valid expressions.
350
[a(x) <= b(x) - 1]
351
352
"""
353
354
if len(invariants)<2 or len(objects)==0: return
355
if not theory: theory=None
356
if not precomputed:
357
precomputed = None
358
elif type(precomputed) == tuple:
359
assert len(precomputed) == 3, 'The length of the precomputed tuple should be 3.'
360
precomputed, object_key, invariant_key = precomputed
361
else:
362
object_key = lambda x: x
363
invariant_key = lambda x: x
364
365
assert 0 <= mainInvariant < len(invariants), 'Illegal value for mainInvariant'
366
367
operatorDict = { '-1' : 'U 0', '+1' : 'U 1', '*2' : 'U 2', '/2' : 'U 3',
368
'^2' : 'U 4', '-()' : 'U 5', '1/' : 'U 6',
369
'sqrt' : 'U 7', 'ln' : 'U 8', 'log10' : 'U 9',
370
'exp' : 'U 10', '10^' : 'U 11', 'ceil' : 'U 12',
371
'floor' : 'U 13', 'abs' : 'U 14', 'sin' : 'U 15',
372
'cos' : 'U 16', 'tan' : 'U 17', 'asin' : 'U 18',
373
'acos' : 'U 19', 'atan' : 'U 20', 'sinh': 'U 21',
374
'cosh' : 'U 22', 'tanh' : 'U 23', 'asinh': 'U 24',
375
'acosh' : 'U 25', 'atanh' : 'U 26',
376
'+' : 'C 0', '*' : 'C 1', 'max' : 'C 2', 'min' : 'C 3',
377
'-' : 'N 0', '/' : 'N 1', '^' : 'N 2'}
378
379
# prepare the invariants to be used in conjecturing
380
invariantsDict = {}
381
names = []
382
383
for pos, invariant in enumerate(invariants):
384
if type(invariant) == tuple:
385
name, invariant = invariant
386
elif hasattr(invariant, '__name__'):
387
if invariant.__name__ in invariantsDict:
388
name = '{}_{}'.format(invariant.__name__, pos)
389
else:
390
name = invariant.__name__
391
else:
392
name = 'invariant_{}'.format(pos)
393
invariantsDict[name] = invariant
394
names.append(name)
395
396
# call the conjecturing program
397
command = './expressions -c{}{} --dalmatian {}--time {} --invariant-names --output stack {} --allowed-skips 0'
398
command = command.format('v' if verbose and debug else '', 't' if theory is not None else '',
399
'--all-operators ' if operators is None else '',
400
time, '--leq' if upperBound else '--geq')
401
402
import subprocess
403
sp = subprocess.Popen(command, shell=True,
404
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
405
stderr=subprocess.PIPE, close_fds=True)
406
stdin = sp.stdin
407
408
if operators is not None:
409
stdin.write('{}\n'.format(len(operators)))
410
for op in operators:
411
stdin.write('{}\n'.format(operatorDict[op]))
412
413
stdin.write('{} {} {}\n'.format(len(objects), len(invariants), mainInvariant + 1))
414
415
for invariant in names:
416
stdin.write('{}\n'.format(invariant))
417
418
def get_value(invariant, o):
419
precomputed_value = None
420
if precomputed:
421
o_key = object_key(o)
422
i_key = invariant_key(invariant)
423
if o_key in precomputed:
424
if i_key in precomputed[o_key]:
425
precomputed_value = precomputed[o_key][i_key]
426
if precomputed_value is None:
427
return invariant(o)
428
else:
429
return precomputed_value
430
431
if theory is not None:
432
for o in objects:
433
if upperBound:
434
try:
435
stdin.write('{}\n'.format(min(float(get_value(t, o)) for t in theory)))
436
except:
437
stdin.write('NaN\n')
438
else:
439
try:
440
stdin.write('{}\n'.format(max(float(get_value(t, o)) for t in theory)))
441
except:
442
stdin.write('NaN\n')
443
444
for o in objects:
445
for invariant in names:
446
try:
447
stdin.write('{}\n'.format(float(get_value(invariantsDict[invariant], o))))
448
except:
449
stdin.write('NaN\n')
450
451
if debug:
452
for l in sp.stderr:
453
print '> ' + l.rstrip()
454
455
# process the output
456
out = sp.stdout
457
458
variable = SR.var(variableName)
459
460
conjectures = []
461
inputList = []
462
463
for l in out:
464
op = l.strip()
465
if op:
466
inputList.append(op)
467
else:
468
conjectures.append(_makeConjecture(inputList, variable, invariantsDict))
469
inputList = []
470
471
return conjectures
472
473
class PropertyBasedConjecture(SageObject):
474
475
def __init__(self, expression, propertyCalculators, pickling):
476
"""Constructs a new Conjecture from the given stack of functions."""
477
self.expression = expression
478
self.propertyCalculators = propertyCalculators
479
self.pickling = pickling
480
self.__name__ = repr(self.expression)
481
super(PropertyBasedConjecture, self).__init__()
482
483
def __eq__(self, other):
484
return self.expression == other.expression
485
486
def __reduce__(self):
487
return (_makePropertyBasedConjecture, self.pickling)
488
489
def _repr_(self):
490
return repr(self.expression)
491
492
def _latex_(self):
493
return latex(self.expression)
494
495
def __call__(self, g):
496
return self.evaluate(g)
497
498
def evaluate(self, g):
499
values = {prop: f(g) for (prop, f) in self.propertyCalculators.items()}
500
return self.expression.evaluate(values)
501
502
def _makePropertyBasedConjecture(inputList, invariantsDict):
503
import operator
504
505
binaryOperators = {'&', '|', '^', '->'}
506
507
expressionStack = []
508
propertyCalculators = {}
509
510
for op in inputList:
511
if op in invariantsDict:
512
import types
513
if type(invariantsDict[op]) in (types.BuiltinMethodType, types.MethodType):
514
f = wrapUnboundMethod(op, invariantsDict)
515
else:
516
f = wrapBoundMethod(op, invariantsDict)
517
prop = ''.join([l for l in op if l.strip()])
518
expressionStack.append(prop)
519
propertyCalculators[prop] = f
520
elif op == '<-':
521
right = expressionStack.pop()
522
left = expressionStack.pop()
523
expressionStack.append('({})->({})'.format(right, left))
524
elif op == '~':
525
expressionStack.append('~({})'.format(expressionStack.pop()))
526
elif op in binaryOperators:
527
right = expressionStack.pop()
528
left = expressionStack.pop()
529
expressionStack.append('({}){}({})'.format(left, op, right))
530
else:
531
raise ValueError("Error while reading output from expressions. Unknown element: {}".format(op))
532
533
import sage.logic.propcalc as propcalc
534
return PropertyBasedConjecture(propcalc.formula(expressionStack.pop()), propertyCalculators, (inputList, invariantsDict))
535
536
def allPropertyBasedOperators():
537
"""
538
Returns a set containing all the operators that can be used with the
539
property-based conjecture method. This method can be used to quickly
540
get a set from which to remove some operators or to just get an idea
541
of how to write some operators.
542
543
There are at the moment 5 operators available, including, e.g., AND::
544
545
>>> len(allPropertyBasedOperators())
546
5
547
>>> '&' in allPropertyBasedOperators()
548
True
549
"""
550
return { '~', '&', '|', '^', '->'}
551
552
def propertyBasedConjecture(objects, properties, mainProperty, time=5, debug=False,
553
verbose=False, sufficient=True, operators=None,
554
theory=None, precomputed=None):
555
"""
556
Runs the conjecturing program for properties with the provided objects,
557
properties and main property. This method requires the program ``expressions``
558
to be in the current working directory of Sage.
559
560
INPUT:
561
562
- ``objects`` - a list of objects about which conjectures should be made.
563
- ``properties`` - a list of functions (callable objects) which take a
564
single argument and return a boolean value. Each function should
565
be able to produce a value for each of the elements of objects.
566
- ``mainProperty`` - an integer that is the index of one of the elements
567
of properties. All conjectures will then be a bound for the property that
568
corresponds to this index.
569
- ``sufficient`` - if given, this boolean value specifies whether sufficient
570
or necessary conditions for the main property should be generated. If
571
``True``, then sufficient conditions are generated. If ``False``, then
572
necessary conditions are generated. The default value is ``True``
573
- ``time`` - if given, this integer specifies the number of seconds before
574
the conjecturing program times out and returns the best conjectures it
575
has at that point. The default value is 5.
576
- ``theory`` - if given, specifies a list of known bounds. If this is
577
``None``, then no known bounds are used. Otherwise each conjecture will
578
have to be more significant than the conditions in this list. The default
579
value is ``None``.
580
- ``operators`` - if given, specifies a set of operators that can be used.
581
If this is ``None``, then all known operators are used. Otherwise only
582
the specified operators are used. It is advised to use the method
583
``allPropertyBasedOperators()`` to get a set containing all operators and
584
then removing the operators which are not needed. The default value is
585
``None``.
586
- ``debug`` - if given, this boolean value specifies whether the output of
587
the program ``expressions`` to ``stderr`` is printed. The default value
588
is ``False``.
589
- ``verbose`` - if given, this boolean value specifies whether the program
590
``expressions`` is ran in verbose mode. Note that this has nu purpose if
591
``debug`` is not also set to ``True``. The default value is ``False``.
592
593
EXAMPLES::
594
595
A very simple example uses just two properties of integers and generates
596
sufficient conditions for an integer to be prime based on the integer 3::
597
598
>>> propertyBasedConjecture([3], [is_prime,is_even], 0)
599
[(~(is_even))->(is_prime)]
600
601
We can also generate necessary condition conjectures::
602
603
>>> propertyBasedConjecture([3], [is_prime,is_even], 0, sufficient=False)
604
[(is_prime)->(~(is_even))]
605
606
In some cases strange conjectures might be produced or one conjecture you
607
might be expecting does not show up. In this case you can use the ``debug``
608
and ``verbose`` option to find out what is going on behind the scene. By
609
enabling ``debug`` the program prints the reason it stopped generating
610
conjectures (time limit, no better conjectures possible, ...) and gives some
611
statistics about the number of conjectures it looked at::
612
613
>>> propertyBasedConjecture([3], [is_prime,is_even], 0, debug=True)
614
> Generation process was stopped by the conjecturing heuristic.
615
> Found 2 unlabeled trees.
616
> Found 2 labeled trees.
617
> Found 2 valid expressions.
618
[(~(is_even))->(is_prime)]
619
620
By also enabling ``verbose``, you can discover which values are actually
621
given to the program::
622
623
>>> propertyBasedConjecture([3], [is_prime,is_even], 0, debug=True, verbose=True)
624
Using the following command
625
./expressions -pcv --dalmatian --all-operators --time 5 --invariant-names --output stack --sufficient --allowed-skips 0
626
> Invariant 1 Invariant 2
627
> 1) TRUE FALSE
628
> Generating trees with 0 unary nodes and 0 binary nodes.
629
> Saving expression
630
> is_prime <- is_even
631
> Status: 1 unlabeled tree, 1 labeled tree, 1 expression
632
> Generating trees with 1 unary node and 0 binary nodes.
633
> Conjecture is more significant for object 1.
634
> Saving expression
635
> is_prime <- ~(is_even)
636
> Conjecture 2 is more significant for object 1.
637
> Status: 2 unlabeled trees, 2 labeled trees, 2 expressions
638
> Generation process was stopped by the conjecturing heuristic.
639
> Found 2 unlabeled trees.
640
> Found 2 labeled trees.
641
> Found 2 valid expressions.
642
[(~(is_even))->(is_prime)]
643
"""
644
645
if len(properties)<2 or len(objects)==0: return
646
if not theory: theory=None
647
if not precomputed:
648
precomputed = None
649
elif type(precomputed) == tuple:
650
assert len(precomputed) == 3, 'The length of the precomputed tuple should be 3.'
651
precomputed, object_key, invariant_key = precomputed
652
else:
653
object_key = lambda x: x
654
invariant_key = lambda x: x
655
656
assert 0 <= mainProperty < len(properties), 'Illegal value for mainProperty'
657
658
operatorDict = { '~' : 'U 0', '&' : 'C 0', '|' : 'C 1', '^' : 'C 2', '->' : 'N 0'}
659
660
# prepare the invariants to be used in conjecturing
661
propertiesDict = {}
662
names = []
663
664
for pos, property in enumerate(properties):
665
if type(property) == tuple:
666
name, property = property
667
elif hasattr(property, '__name__'):
668
if property.__name__ in propertiesDict:
669
name = '{}_{}'.format(property.__name__, pos)
670
else:
671
name = property.__name__
672
else:
673
name = 'property_{}'.format(pos)
674
propertiesDict[name] = property
675
names.append(name)
676
677
# call the conjecturing program
678
command = './expressions -pc{}{} --dalmatian {}--time {} --invariant-names --output stack {} --allowed-skips 0'
679
command = command.format('v' if verbose and debug else '', 't' if theory is not None else '',
680
'--all-operators ' if operators is None else '',
681
time, '--sufficient' if sufficient else '--necessary')
682
683
if verbose:
684
print('Using the following command')
685
print(command)
686
687
import subprocess
688
sp = subprocess.Popen(command, shell=True,
689
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
690
stderr=subprocess.PIPE, close_fds=True)
691
stdin = sp.stdin
692
693
if operators is not None:
694
stdin.write('{}\n'.format(len(operators)))
695
for op in operators:
696
stdin.write('{}\n'.format(operatorDict[op]))
697
698
stdin.write('{} {} {}\n'.format(len(objects), len(properties), mainProperty + 1))
699
700
for property in names:
701
stdin.write('{}\n'.format(property))
702
703
def get_value(prop, o):
704
precomputed_value = None
705
if precomputed:
706
o_key = object_key(o)
707
p_key = invariant_key(prop)
708
if o_key in precomputed:
709
if p_key in precomputed[o_key]:
710
precomputed_value = precomputed[o_key][p_key]
711
if precomputed_value is None:
712
return prop(o)
713
else:
714
return precomputed_value
715
716
if theory is not None:
717
for o in objects:
718
if sufficient:
719
try:
720
stdin.write('{}\n'.format(max((1 if bool(get_value(t, o)) else 0) for t in theory)))
721
except:
722
stdin.write('-1\n')
723
else:
724
try:
725
stdin.write('{}\n'.format(min((1 if bool(get_value(t, o)) else 0) for t in theory)))
726
except:
727
stdin.write('-1\n')
728
729
for o in objects:
730
for property in names:
731
try:
732
stdin.write('{}\n'.format(1 if bool(get_value(propertiesDict[property], o)) else 0))
733
except:
734
stdin.write('-1\n')
735
736
if debug:
737
for l in sp.stderr:
738
print '> ' + l.rstrip()
739
740
# process the output
741
out = sp.stdout
742
743
conjectures = []
744
inputList = []
745
746
for l in out:
747
op = l.strip()
748
if op:
749
inputList.append(op)
750
else:
751
conjectures.append(_makePropertyBasedConjecture(inputList, propertiesDict))
752
inputList = []
753
754
return conjectures
755
756