forked from sympy/sympy
/
assumptions.py
374 lines (266 loc) · 11.7 KB
/
assumptions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
from facts import FactRules
class CycleDetected(Exception):
"""(internal) used to detect cycles when evaluating assumptions
through prerequisites
"""
pass
class AssumeMeths(object):
""" Define default assumption methods.
AssumeMeths should be used to derive Basic class only.
All symbolic objects have assumption attributes that can be accessed via
.is_<assumption name> attribute.
Assumptions determine certain properties of symbolic objects. Assumptions
can have 3 possible values: True, False, None. None is returned when it is
impossible to say something about the property. For example, a Symbol is
not know beforehand to be positive.
By default, all symbolic values are in the largest set in the given context
without specifying the property. For example, a symbol that has a property
being integer, is also real, complex, etc.
Here follows a list of possible assumption names:
- commutative - object commutes with any other object with
respect to multiplication operation.
- real - object can have only values from the set
of real numbers
- integer - object can have only values from the set
of integers
- bounded - object absolute value is bounded
- positive - object can have only positive values
- negative - object can have only negative values
- nonpositive - object can have only nonpositive values
- nonnegative - object can have only nonnegative values
- comparable - object.evalf() returns Number object.
- irrational - object value cannot be represented exactly by Rational
- unbounded - object value is arbitrarily large
- infinitesimal - object value is infinitesimal
Example rules:
positive=T -> nonpositive=F, real=T
real=T & positive=F -> nonpositive=T
unbounded=F|T -> bounded=not unbounded XXX ok?
irrational=T -> real=T
Implementation note: assumption values are stored in
._assumption dictionary or are returned by getter methods (with
property decorators) or are attributes of objects/classes.
Examples:
- True, when we are sure about a property. For example, when we are
working only with real numbers:
>>> from sympy import *
>>> Symbol('x', real = True)
x
- False
- None (if you don't know if the property is True or false)
"""
__slots__ = ['_assumptions', # assumptions
'_a_inprogress', # already-seen requests (when deducing
# through prerequisites -- see CycleDetected)
]
# This are the rules under which our assumptions function
#
# References
# ----------
#
# negative, -- http://en.wikipedia.org/wiki/Negative_number
# nonnegative
#
# even, odd -- http://en.wikipedia.org/wiki/Parity_(mathematics)
# imaginary -- http://en.wikipedia.org/wiki/Imaginary_number
# composite -- http://en.wikipedia.org/wiki/Composite_number
# finite -- http://en.wikipedia.org/wiki/Finite
# infinitesimal -- http://en.wikipedia.org/wiki/Infinitesimal
# irrational -- http://en.wikipedia.org/wiki/Irrational_number
# ...
_assume_rules = FactRules([
'integer -> rational',
'rational -> real',
'real -> complex',
'imaginary -> complex',
'complex -> commutative',
'odd == integer & !even',
'even == integer & !odd',
'real == negative | zero | positive',
'positive -> real & !negative & !zero',
'negative -> real & !positive & !zero',
'nonpositive == real & !positive',
'nonnegative == real & !negative',
'zero -> infinitesimal & even',
'prime -> integer & positive',
'composite == integer & positive & !prime',
'irrational == real & !rational',
'imaginary -> !real',
'!bounded == unbounded',
'noninteger == real & !integer',
'!zero == nonzero',
# XXX do we need this ?
'finite -> bounded', # XXX do we need this?
'finite -> !zero', # XXX wrong?
'infinitesimal -> !finite', # XXX is this ok?
])
_assume_defined = _assume_rules.defined_facts.copy()
_assume_defined.add('comparable')
_assume_defined = frozenset(_assume_defined)
###################################
# positive/negative from .evalf() #
###################################
# properties that indicate ordering on real axis
_real_ordering = set(['negative', 'nonnegative', 'positive', 'nonpositive'])
# what can be said from cmp(x.evalf(),0)
# NOTE: if x.evalf() is zero we can say nothing
_real_cmp0_table= {
'positive': {1: True, -1: False, 0: None},
'negative': {1: False, -1: True, 0: None},
}
# because we can say nothing if x.evalf() is zero, nonpositive is the same
# as negative
_real_cmp0_table['nonpositive'] = _real_cmp0_table['negative']
_real_cmp0_table['nonnegative'] = _real_cmp0_table['positive']
def __getstate__(self, cls=None):
if cls is None:
# This is the case for the instance that gets pickled
cls = self.__class__
d = {}
# Get all data that should be stored from super classes
for c in cls.__bases__:
if hasattr(c, "__getstate__"):
d.update(c.__getstate__(self, c))
# Get all information that should be stored from cls and return the dic
for name in cls.__slots__:
if hasattr(self, name):
d[name] = getattr(self, name)
return d
def __setstate__(self, d):
# All values that were pickled are now assigned to a fresh instance
for name, value in d.iteritems():
try:
setattr(self, name, value)
except:
pass
def _what_known_about(self, k):
"""tries hard to give an answer to: what is known about fact `k`
NOTE: You should not use this directly -- see make__get_assumption
instead
This function is called when a request is made to see what a fact
value is.
If we are here, it means that the asked-for fact is not known, and
we should try to find a way to find it's value.
For this we use several techniques:
1. _eval_is_<fact>
------------------
first fact-evalation function is tried, for example
_eval_is_integer
2. relations
------------
if the first step did not succeeded (no such function, or its return
is None) then we try related facts. For example
means
rational --> integer
another example is joined rule:
integer & !odd --> even
so in the latter case if we are looking at what 'even' value is,
'integer' and 'odd' facts will be asked.
3. evalf() for comparable
-------------------------
as a last resort for comparable objects we get their numerical value
-- this helps to determine facts like 'positive' and 'negative'
In all cases when we settle on some fact value, it is given to
_learn_new_facts to deduce all its implications, and also the result
is cached in ._assumptions for later quick access.
"""
# 'defined' assumption
if k not in self._assume_defined:
raise AttributeError('undefined assumption %r' % (k))
assumptions = self._assumptions
seen = self._a_inprogress
#print '%s=?\t%s %s' % (name, self,seen)
if k in seen:
raise CycleDetected
seen.append(k)
try:
# First try the assumption evaluation function if it exists
if hasattr(self, '_eval_is_'+k):
#print 'FWDREQ: %s\t%s' % (self, k)
try:
a = getattr(self,'_eval_is_'+k)()
# no luck - e.g. is_integer -> ... -> is_integer
except CycleDetected:
#print 'CYC'
pass
else:
if a is not None:
self._learn_new_facts( ((k,a),) )
return a
# Try assumption's prerequisites
for pk in self._assume_rules.prereq.get(k,()):
#print 'pk: %s' % pk
if hasattr(self, '_eval_is_'+pk):
# cycle
if pk in seen:
continue
#print 'PREREQ: %s\t%s <- %s' % (self, k, pk)
a = getattr(self,'is_'+pk)
if a is not None:
self._learn_new_facts( ((pk,a),) )
# it is possible that we either know or don't know k at
# this point
try:
return self._assumptions[k]
except KeyError:
pass
finally:
seen.pop()
# For positive/negative try to ask evalf
if k in self._real_ordering:
if self.is_comparable:
v = self.evalf()
c = cmp(v, 0)
a = self._real_cmp0_table[k][c]
if a is not None:
self._learn_new_facts( ((k,a),) )
return a
# No result -- unknown
# cache it (NB ._learn_new_facts(k, None) to learn other properties,
# and because assumptions may not be detached)
self._learn_new_facts( ((k,None),) )
return None
def _learn_new_facts(self, facts):
"""Learn new facts about self.
*******************************************************************
* internal routine designed to be used only from assumptions core *
*******************************************************************
Given new facts and already present knowledge (._assumptions) we ask
inference engine to derive full set of new facts which follow from
this combination.
The result is stored back into ._assumptions
"""
# no new facts
if not facts:
return
default_assumptions = type(self).default_assumptions
base = self._assumptions
# ._assumptions were shared with the class
if base is default_assumptions:
base = base.copy()
self._assumptions = base
self._assume_rules.deduce_all_facts(facts, base)
else:
# NOTE it modifies base inplace
self._assume_rules.deduce_all_facts(facts, base)
def make__get_assumption(classname, name):
"""Cooks function which will get named assumption
e.g.
class C:
is_xxx = make__get_assumption('C', 'xxx')
is_yyy = property( make__get_assumption('C', 'yyy'))
then
c = C()
c.is_xxx() # note braces -- it's function call
c.is_yyy # no braces -- it's property
"""
def getit(self):
try:
return self._assumptions[name]
except KeyError:
return self._what_known_about(name)
getit.func_name = '%s__is_%s' % (classname, name)
#print '\n\n\n%s\n' % getit
#from dis import dis
#dis(getit)
return getit