1 # -*- coding: latin-1 -*-
3 This module implements extension type Expr that holds two Python
4 objects, head and data, in a pair attribute.
6 When adding new features to Expr class, make sure that these are
7 added also to extension type Expr in src/expr_ext.c.
11 >>> from sympycore.expr_ext import *
13 10000000 loops, best of 3: 179 ns per loop
15 >>> %timeit h = e.head
16 10000000 loops, best of 3: 113 ns per loop
17 >>> %timeit h,d = e.pair
18 10000000 loops, best of 3: 141 ns per loop
22 >>> from sympycore.expr import *
24 1000000 loops, best of 3: 988 ns per loop
26 >>> %timeit h = e.head
27 10000000 loops, best of 3: 119 ns per loop
28 >>> %timeit h,d = e.pair
29 10000000 loops, best of 3: 139 ns per loop
31 # Author: Pearu Peterson
34 __all__
= ['Expr', 'Pair']
37 from .core
import heads
38 for n
,h
in heads
.iterNameValue(): setattr(m
, n
, h
)
39 from .arithmetic
.numbers
import inttypes
, numbertypes
, try_power
, gcd
41 m
.numbertypes
= numbertypes
42 m
.try_power
= try_power
46 """Represents an symbolic expression in a pair form: (head, data)
48 The pair (head, data) is saved in an attribute ``pair``. The parts of
49 a pair, head and data, can be also accessed via ``head`` and ``data``
50 attributes, respectively. All three attributes are read-only.
52 The head part is assumed to be an immutable object. The data part can
53 be either an immutable object or Python dictionary. In the former
54 case, the hash value of Expr instance is defined as::
56 hash((<Expr>.head, <Expr>.
58 Otherwise, if ``data`` contains a Python dictionary, then the hash
61 hash((<Expr>.head, frozenset(<Expr>.data.items())
63 If ``data`` is a Python list, then the hash value is::
65 hash((<Expr>.head, tuple(<Expr>.data)))
67 WARNING: the hash value of an Expr instance is computed (and cached)
68 when it is used as a key to Python dictionary. This means that the
69 instance content MUST NOT be changed after the hash is computed. To
70 check if it is safe to change the ``data`` dictionary, use
71 ``is_writable`` attribute that returns True when the hash value has
74 <Expr>.is_writable -> True or False
76 There are two ways to access the parts of a Expr instance from
79 a = Expr(<head>, <data>)
80 head, data = a.head, a.data - for backward compatibility
81 head, data = a.pair - fastest way
83 When Expr constructor is called with one argument, say ``x``, then
84 ``<Expr subclass>.convert(x)`` will be returned.
86 This is Python version of Expr type.
89 __slots__
= ['head', 'data', 'pair', '_hash']
91 def __init__(self
, *args
):
93 obj
= self
.convert(args
[0])
95 self
._hash
= obj
._hash
100 raise TypeError("%s requires 1 or 2 arguments but got %r" % (type(self
), len(args
)))
101 msg
= self
.head
.is_data_ok(type(self
), self
.data
)
103 msg
= '%s(head=%s, data=%s): %s' % (type(self
).__name
__, self
.head
, self
.data
, msg
)
108 return '%s%r' % (type(self
).__name
__, self
.pair
)
111 """ Compute hash value.
113 Different from expr_ext.Expr, an exception is raised when data
114 dictionary values contain dictionaries.
119 obj
= self
.as_lowlevel()
126 h
= hash((head
, frozenset(data
.iteritems())))
128 h
= hash((head
, tuple(data
)))
135 def is_writable(self
, _writable_types
= (list, dict)):
136 if self
._hash
is None:
139 if tdata
in _writable_types
:
142 return data
.is_writable
154 def _sethash(self
, hashvalue
):
155 """ Set hash value for the object.
157 If hashvalue==-1, then the hash value will be reset.
159 Used by pickle support in sympycore.core._reconstruct. DO NOT
160 use this method directly.
165 self
._hash
= hashvalue
167 def __reduce__(self
):
168 # see also _reconstruct function in sympycore/core.py
170 from sympycore
.core
import _reconstruct
172 hashvalue
= self
._hash
173 if hashvalue
is None:
175 state
= (type(self
), self
.pair
, hashvalue
)
176 elif version
==2 or version
==3:
177 hashvalue
= self
._hash
178 if hashvalue
is None:
183 args
= typ
.__getinitargs
__(cls
)
184 except AttributeError:
187 # either metaclass does not define __getinitargs__ method
188 # or cls has no metaclass
189 state
= (cls
, self
.pair
, hashvalue
)
191 state
= ((typ
, args
), self
.pair
, hashvalue
)
193 raise NotImplementedError('pickle state version %s' % (version
))
194 return _reconstruct
, (version
, state
)
196 def __nonzero__(self
):
197 # Note that `not cls(MUL, [])` would return True while `cls(MUL, [])==1`.
198 # So, must use as_lowlevel:
199 obj
= self
.as_lowlevel()
200 if obj
is not self
.pair
:
202 return not not self
.data
204 def as_lowlevel(self
):
205 """ Return self as low-level object instance that will be used
206 in comparison and in hash computation.
208 By default, as_lowlevel uses heads to_lowlevel(cls, data, pair)
209 method but since as_lowlevel is a most frequently called
210 method then for some heads the corresponding code is copied
211 here. The default return value is a pair tuple for composite
212 objects and data part for atomic objects. The as_lowlevel
213 method may also return an Expr instance but not self
214 (otherwise infinite recurrsion will occur).
216 See __hash__, __nonzero__ method for more details how the
217 results of as_lowlevel method are interpreted.
219 head
, data
= pair
= self
.pair
220 if head
is NUMBER
or head
is SYMBOL
or head
is SPECIAL
:
222 elif head
is MUL
or head
is DIV
:
228 elif head
is ADD
or head
is SUB
:
236 if exp
==0 or base
==1:
240 elif head
is TERM_COEFF
:
248 elif head
is TERM_COEFF_DICT
:
253 return type(self
)(TERM_COEFF
, dict_get_item(data
))
254 elif head
is BASE_EXP_DICT
:
259 return type(self
)(POW
, dict_get_item(data
))
261 return head
.to_lowlevel(type(self
), data
, pair
)
264 def term_coeff(self
):
265 head
, data
= self
.pair
266 if head
is TERM_COEFF
:
268 if head
is BASE_EXP_DICT
:
270 coeff
= base_exp_dict_get_coefficient(cls
, data
)
271 if coeff
is not None:
274 r
= BASE_EXP_DICT
.new(cls
, d
)
276 t
, c
= r
.head
.term_coeff(cls
, r
)
284 head
, data
= self
.pair
289 for _item
in dict(__eq__
= '==', __ne__
= '!=',
290 __lt__
= '<', __le__
= '<=',
291 __gt__
= '>', __ge__
= '>=',
295 if type(self) is type(other):
296 other = other.as_lowlevel()
297 return self.as_lowlevel() %s other
302 """ Holds a pair that may contain list and dict second element.
304 def __init__(self
, *args
):
309 raise TypeError("%s requires 2 arguments but got %r" % (type(self
), len(args
)))
311 def __eq__(self
, other
):
312 return self
.pair
== other
317 def __getitem__(self
, index
):
318 return self
.pair
[index
]
321 def dict_mul_item(Algebra
, d
, key
, value
):
328 base_exp_dict_mul_item
= dict_mul_item
330 def term_coeff_dict_mul_item(Algebra
, d
, key
, value
):
331 return dict_mul_item(Algebra
, d
, key
, value
)
333 def dict_add_item(Algebra
, d
, key
, value
):
345 def base_exp_dict_get_coefficient(Algebra
, d
):
346 for k
, v
in d
.iteritems():
347 if v
is 1 and k
.head
is NUMBER
:
351 def base_exp_dict_add_item(Algebra
, d
, base
, exp
):
353 d is a dictonary that will be updated with base, exp pair.
354 Base part must be an Algebra instance while exp part
355 can either be a number instance or an expression.
357 The dictionary items (base, exp) must satisfy the following
359 1) numeric (base, exp) must be completely evaluated, that is,
360 if base is numeric then exp is either 1 or rational with denominator
361 part that is not a root of base.
362 2) all numeric items with exp==1 must be combined to one item
364 3) items with base==1 and exp==0 must not be present in the dictionary
366 base_head
, base_data
= base
.pair
368 if type(exp
) is Algebra
and exp
.head
is NUMBER
:
371 if base_head
is NUMBER
:
376 coeff
= base_exp_dict_get_coefficient(Algebra
, d
)
386 assert not isinstance(exp
, inttypes
),`base
, exp`
393 if type(value
) is Algebra
and value
.head
is NUMBER
:
396 if base_head
is NUMBER
and isinstance(value
, numbertypes
):
398 r
, l
= try_power(base_data
, value
)
400 base_exp_dict_add_item(Algebra
, d
, Algebra(NUMBER
, r
), 1)
401 elif len(l
)==1 and l
[0]==base_data
:
405 base_exp_dict_add_item(Algebra
, d
, Algebra(NUMBER
, b
), e
)
406 elif base_head
is TERM_COEFF
and isinstance(value
, inttypes
):
408 term
, coeff
= base_data
409 base_exp_dict_add_item(Algebra
, d
, term
, value
)
410 base_exp_dict_add_item(Algebra
, d
, Algebra(NUMBER
, coeff
), value
)
416 def term_coeff_dict_add_item(Algebra
, d
, key
, value
):
417 khead
, kdata
= key
.pair
418 if khead
is TERM_COEFF
:
420 dict_add_item(Algebra
, d
, term
, value
* coeff
)
422 assert khead
not in [TERM_COEFF_DICT
],`Algebra
, key
.pair`
423 dict_add_item(Algebra
, d
, key
, value
)
425 def term_coeff_dict_add_dict(Algebra
, dict1
, dict2
):
426 for key
, value
in dict2
.iteritems():
427 term_coeff_dict_add_item(Algebra
, dict1
, key
, value
)
429 def dict_get_item(d
):
432 def dict_add_dict(Algebra
, dict1
, dict2
):
433 for key
, value
in dict2
.iteritems():
434 dict_add_item(Algebra
, dict1
, key
, value
)
436 def base_exp_dict_add_dict(Algebra
, dict1
, dict2
):
437 for key
, value
in dict2
.iteritems():
438 base_exp_dict_add_item(Algebra
, dict1
, key
, value
)
440 def base_exp_dict_sub_item(Algebra
, dict, key
, value
):
441 return base_exp_dict_add_item(Algebra
, dict, key
, -value
)
443 def base_exp_dict_sub_dict(Algebra
, dict1
, dict2
):
444 for key
, value
in dict2
.iteritems():
445 base_exp_dict_sub_item(Algebra
, dict1
, key
, value
)
447 def dict_sub_dict(Algebra
, dict1
, dict2
):
448 for key
, value
in dict2
.iteritems():
449 dict_add_item(Algebra
, dict1
, key
, -value
)
451 def base_exp_dict_mul_dict(Algebra
, d
, dict1
, dict2
):
452 for t1
,c1
in dict1
.iteritems():
453 for t2
,c2
in dict2
.iteritems():
455 t
, c
= t
.term_coeff()
457 base_exp_dict_add_item(Algebra
, d
, t
, c12
)
459 def exp_coeff_dict_mul_dict(Algebra
, d
, dict1
, dict2
):
460 for t1
,c1
in dict1
.iteritems():
461 for t2
,c2
in dict2
.iteritems():
464 dict_add_item(Algebra
, d
, t
, c12
)
466 def dict_mul_dict(Algebra
, d
, dict1
, dict2
):
467 for t1
,c1
in dict1
.iteritems():
468 for t2
,c2
in dict2
.iteritems():
470 #t, c = t.term_coeff()
472 dict_add_item(Algebra
, d
, t
, c12
)
474 def dict_mul_value(Algebra
, d
, value
):
477 for t
, c
in d
.items():
484 term_coeff_dict_mul_dict
= dict_mul_dict
485 base_exp_dict_mul_value
= dict_mul_value
486 term_coeff_dict_mul_value
= dict_mul_value
488 def term_coeff_new(Algebra
, data
):
493 return Algebra(NUMBER
, coeff
)
495 return Algebra(NUMBER
, 0)
499 return term_coeff_new(Algebra
, (t
, c
* coeff
))
501 return Algebra(NUMBER
, d
* coeff
)
502 return Algebra(TERM_COEFF
, data
)
504 def term_coeff_dict_new(Algebra
, data
):
506 Return canonicalized TERM_COEFF_DICT expression from data.
510 return Algebra(TERM_COEFF_DICT
, data
)
512 return Algebra(NUMBER
, 0)
513 return term_coeff_new(Algebra
, dict_get_item(data
))
515 def term_coeff_dict(Algebra
, expr
):
517 Return canonicalized TERM_COEFF_DICT expression from existing
520 * if expr has no data, return 0
521 * if expr data has one item, return TERM_COEFF expression
528 return Algebra(NUMBER
, 0)
529 return term_coeff_new(Algebra
, dict_get_item(data
))
531 def pow_new(Algebra
, data
):
535 if base
==1 or exp
==0:
536 return Algebra(NUMBER
, 1)
537 #if type(exp) is Algebra and exp.head is NUMBER:
538 # data = base, exp.data
539 return Algebra(POW
, data
)
541 def base_exp_dict_new(Algebra
, data
):
544 return Algebra(NUMBER
, 1)
546 return pow_new(Algebra
, dict_get_item(data
))
547 coeff
= base_exp_dict_get_coefficient(Algebra
, data
)
549 return Algebra(BASE_EXP_DICT
, data
)
551 return term_coeff_new(Algebra
, (base_exp_dict_new(Algebra
, data
), coeff
.data
))
553 def add_new(Algebra
, data
):
556 return Algebra(NUMBER
, 0)
559 return Algebra(ADD
, data
)
561 def add(Algebra
, expr
):
565 return Algebra(NUMBER
, 0)
570 def mul_new(Algebra
, data
):
573 return Algebra(NUMBER
, 1)
576 return Algebra(MUL
, data
)