2 from basic
import Basic
, S
, C
, sympify
3 from operations
import AssocOp
4 from methods
import RelMeths
, ArithMeths
5 from cache
import cacheit
7 from logic
import fuzzy_not
9 from symbol
import Symbol
, Wild
10 # from function import FunctionClass, WildFunction /cyclic/
11 # from numbers import Number, Integer, Real /cyclic/
12 # from add import Add /cyclic/
13 # from power import Pow /cyclic/
15 import sympy
.mpmath
as mpmath
17 class Mul(AssocOp
, RelMeths
, ArithMeths
):
24 def flatten(cls
, seq
):
25 # apply associativity, separate commutative part of seq
32 coeff
= S
.One
# standalone term
35 c_powers
= {} # base -> exp z
36 # e.g. (x+y) -> z for ... * (x+y) * ...
38 exp_dict
= {} # num-base -> exp y
39 # e.g. 3 -> y for ... * 3 * ...
41 inv_exp_dict
= {} # exp -> Mul(num-bases) x x
42 # e.g. x -> 6 for ... * 2 * 3 * ...
48 while c_seq
or nc_seq
:
52 # first process commutative objects
54 if isinstance(o
, FunctionClass
):
55 if o
.nargs
is not None:
56 o
, lambda_args
= o
.with_dummy_arguments(lambda_args
)
60 o
, order_symbols
= o
.as_expr_symbols(order_symbols
)
65 c_seq
= list(o
.args
[:]) + c_seq
76 base
, exponent
= o
.as_base_exp()
82 # let's collect factors with numeric base
84 exp_dict
[base
] += exponent
86 exp_dict
[base
] = exponent
91 # exp(x) / exp(y) -> exp(x-y)
97 b
, e
= o
.as_base_exp()
103 # (-3 + y) -> (-1) * (3 - y)
104 if b
.is_Add
and e
.is_Number
:
105 #found factor (x+y)**number; split off initial coefficient
106 c
, t
= b
.as_coeff_terms()
107 #last time I checked, Add.as_coeff_terms returns One or NegativeOne
108 #but this might change
109 if c
.is_negative
and not e
.is_integer
:
110 # extracting root from negative number: ignore sign
111 if c
is not S
.NegativeOne
:
112 # make c positive (probably never occurs)
116 #else: ignoring sign from NegativeOne: nothing to do!
121 #else: c is One, so pass
123 # let's collect factors with the same base, so e.g.
135 if isinstance(o
, WildFunction
):
137 elif isinstance(o
, FunctionClass
):
138 if o
.nargs
is not None:
139 o
, lambda_args
= o
.with_dummy_arguments(lambda_args
)
141 o
, order_symbols
= o
.as_expr_symbols(order_symbols
)
145 # separate commutative symbols
150 if o
.__class
__ is cls
:
152 nc_seq
= list(o
.args
) + nc_seq
159 # try to combine last terms: a * a -> a
161 b1
,e1
= o1
.as_base_exp()
162 b2
,e2
= o
.as_base_exp()
164 nc_seq
.insert(0, b1
** (e1
+ e2
))
170 # ................................
177 for b
, e
in c_powers
.items():
186 elif e
.is_Integer
and b
.is_Number
:
189 c_part
.append(Pow(b
, e
))
193 for b
,e
in exp_dict
.items():
194 if e
in inv_exp_dict
:
199 for e
,b
in inv_exp_dict
.items():
208 elif e
.is_Integer
and b
.is_Number
:
219 # (oo|nan|zero) * ...
220 if (coeff
is S
.Infinity
) or (coeff
is S
.NegativeInfinity
):
237 new_nc_part
.append(t
)
238 nc_part
= new_nc_part
239 c_part
.insert(0, coeff
)
240 elif (coeff
is S
.Zero
) or (coeff
is S
.NaN
):
241 c_part
, nc_part
= [coeff
], []
244 c_part
, nc_part
= [coeff
], []
245 elif coeff
!= Real(1):
246 c_part
.insert(0, coeff
)
247 elif coeff
is not S
.One
:
248 c_part
.insert(0, coeff
)
250 # order commutative part canonically
251 c_part
.sort(Basic
.compare
)
254 if len(c_part
)==2 and c_part
[0].is_Number
and c_part
[1].is_Add
:
255 # 2*(1+a) -> 2 + 2 * a
257 c_part
= [Add(*[coeff
*f
for f
in c_part
[1].args
])]
259 return c_part
, nc_part
, lambda_args
, order_symbols
262 def _eval_power(b
, e
):
266 # (a*b)**2 -> a**2 * b**2
267 return Mul(*[s
**e
for s
in b
.args
])
270 coeff
, rest
= b
.as_coeff_terms()
274 return (-coeff
)**e
* Mul(*((S
.NegativeOne
,) +rest
))**e
276 return coeff
**e
* Mul(*[s
**e
for s
in rest
])
279 coeff
, rest
= b
.as_coeff_terms()
280 if coeff
is not S
.One
:
281 # (2*a)**3 -> 2**3 * a**3
282 return coeff
**e
* Mul(*[s
**e
for s
in rest
])
284 coeff
, rest
= b
.as_coeff_terms()
285 l
= [s
**e
for s
in rest
]
288 return coeff
**e
* Mul(*l
)
290 c
,t
= b
.as_coeff_terms()
291 if e
.is_even
and c
.is_Number
and c
< 0:
292 return (-c
* Mul(*t
)) ** e
295 # return Mul(*[t**e for t in b])
297 def _eval_evalf(self
, prec
):
298 return AssocOp
._eval
_evalf
(self
, prec
).expand()
301 def precedence(self
):
302 coeff
, rest
= self
.as_coeff_terms()
303 if coeff
.is_negative
: return Basic
.Add_precedence
304 return Basic
.Mul_precedence
306 def tostr(self
, level
= 0):
307 precedence
= self
.precedence
308 coeff
, terms
= self
.as_coeff_terms()
309 if coeff
.is_negative
:
311 if coeff
is not S
.One
:
312 terms
= (coeff
,) + terms
313 if isinstance(terms
, Basic
):
315 r
= '-' + '*'.join([t
.tostr(precedence
) for t
in terms
])
317 r
= '*'.join([t
.tostr(precedence
) for t
in self
.args
])
318 r
= r
.replace('*1/', '/')
319 if precedence
<= level
:
323 numer
,denom
= self
.as_numer_denom()
326 coeff
, rest
= self
.as_coeff_terms()
327 r
= delim
.join([s
.tostr(precedence
) for s
in rest
.args
])
330 elif -coeff
is S
.One
:
332 elif coeff
.is_negative
:
333 r
= '-' + (-coeff
).tostr() + delim
+ r
335 r
= coeff
.tostr() + delim
+ r
338 r
= '(' + numer
.tostr() + ') / (' + denom
.tostr() + ')'
340 r
= '(' + numer
.tostr() + ') / ' + denom
.tostr()
341 if precedence
<=level
:
346 def as_two_terms(self
):
347 if len(self
.args
) == 1:
349 return self
.args
[0], Mul(*self
.args
[1:])
352 def as_coeff_terms(self
, x
=None):
361 return Mul(*l1
), tuple(l2
)
364 return coeff
, self
.args
[1:]
365 return S
.One
, self
.args
368 def _expandsums(sums
):
373 left
= Mul
._expandsums
(sums
[:L
//2])
374 right
= Mul
._expandsums
(sums
[L
//2:])
375 if isinstance(right
, Basic
):
377 if isinstance(left
, Basic
):
380 if len(left
) == 1 and len(right
) == 1:
381 # no expansion needed, bail out now to avoid infinite recursion
382 return [Mul(left
[0], right
[0])]
387 terms
.append(Mul(a
,b
).expand())
390 terms
= list(added
.args
)
395 def _eval_expand_basic(self
):
396 plain
, sums
, rewrite
= [], [], False
398 for factor
in self
.args
:
399 terms
= factor
._eval
_expand
_basic
()
401 if terms
is not None:
408 if factor
.is_commutative
:
411 sums
.append([factor
])
413 if terms
is not None:
420 terms
= Mul
._expandsums
(sums
)
422 if isinstance(terms
, Basic
):
427 return Add(*(Mul(plain
, term
) for term
in terms
), **self
.assumptions0
)
429 return Mul(*plain
, **self
.assumptions0
)
431 def _eval_derivative(self
, s
):
432 terms
= list(self
.args
)
434 for i
in xrange(len(terms
)):
438 factors
.append(Mul(*(terms
[:i
]+[t
]+terms
[i
+1:])))
441 def _matches_simple(pattern
, expr
, repl_dict
):
442 # handle (w*3).matches('x*5') -> {w: x*5/3}
443 coeff
, terms
= pattern
.as_coeff_terms()
445 return terms
[0].matches(expr
/ coeff
, repl_dict
)
448 def matches(pattern
, expr
, repl_dict
={}, evaluate
=False):
450 if pattern
.is_commutative
and expr
.is_commutative
:
451 return AssocOp
._matches
_commutative
(pattern
, expr
, repl_dict
, evaluate
)
452 # todo for commutative parts, until then use the default matches method for non-commutative products
453 return Basic
.matches(pattern
, expr
, repl_dict
, evaluate
)
456 def _combine_inverse(lhs
, rhs
):
461 def as_powers_dict(self
):
462 return dict([ term
.as_base_exp() for term
in self
])
464 def as_numer_denom(self
):
465 numers
, denoms
= [],[]
467 n
,d
= t
.as_numer_denom()
470 return Mul(*numers
), Mul(*denoms
)
473 def count_ops(self
, symbolic
=True):
475 return Add(*[t
.count_ops(symbolic
) for t
in self
[:]]) + Symbol('MUL') * (len(self
[:])-1)
476 return Add(*[t
.count_ops(symbolic
) for t
in self
.args
[:]]) + (len(self
.args
)-1)
478 def _eval_is_polynomial(self
, syms
):
479 for term
in self
.args
:
480 if not term
._eval
_is
_polynomial
(syms
):
484 _eval_is_bounded
= lambda self
: self
._eval
_template
_is
_attr
('is_bounded')
485 _eval_is_commutative
= lambda self
: self
._eval
_template
_is
_attr
('is_commutative')
486 _eval_is_integer
= lambda self
: self
._eval
_template
_is
_attr
('is_integer')
487 _eval_is_comparable
= lambda self
: self
._eval
_template
_is
_attr
('is_comparable')
490 # I*I -> R, I*I*I -> -I
492 def _eval_is_real(self
):
505 elif fuzzy_not(t_real
):
514 return (im_count
% 2 == 0)
517 def _eval_is_imaginary(self
):
531 return (im_count
% 2 == 1)
535 def _eval_is_irrational(self
):
542 def _eval_is_positive(self
):
543 terms
= [t
for t
in self
.args
if not t
.is_positive
]
552 if c
.is_negative
and r
.is_negative
:
554 if r
.is_negative
and c
.is_negative
:
556 # check for nonpositivity, <=0
557 if c
.is_negative
and r
.is_nonnegative
:
559 if r
.is_negative
and c
.is_nonnegative
:
561 if c
.is_nonnegative
and r
.is_nonpositive
:
563 if r
.is_nonnegative
and c
.is_nonpositive
:
567 def _eval_is_negative(self
):
568 terms
= [t
for t
in self
.args
if not t
.is_positive
]
570 # all terms are either positive -- 2*Symbol('n', positive=T)
571 # or unknown -- 2*Symbol('x')
580 # check for nonnegativity, >=0
581 if c
.is_negative
and r
.is_nonpositive
:
583 if r
.is_negative
and c
.is_nonpositive
:
585 if c
.is_nonpositive
and r
.is_nonpositive
:
587 if c
.is_nonnegative
and r
.is_nonnegative
:
590 def _eval_is_odd(self
):
591 is_integer
= self
.is_integer
603 elif is_integer
== False:
607 def _eval_is_even(self
):
608 is_integer
= self
.is_integer
611 return fuzzy_not(self
._eval
_is
_odd
())
613 elif is_integer
== False:
616 def _eval_subs(self
, old
, new
):
619 if isinstance(old
, FunctionClass
):
620 return self
.__class
__(*[s
.subs(old
, new
) for s
in self
.args
])
621 coeff1
,terms1
= self
.as_coeff_terms()
622 coeff2
,terms2
= old
.as_coeff_terms()
623 if terms1
==terms2
: # (2*a).subs(3*a,y) -> 2/3*y
624 return new
* coeff1
/coeff2
625 l1
,l2
= len(terms1
),len(terms2
)
627 # if old is just a number, go through the self.args one by one
628 return Mul(*[x
.subs(old
, new
) for x
in self
.args
])
630 # old is some something more complex, like:
631 # (a*b*c*d).subs(b*c,x) -> a*x*d
632 # then we need to search where in self.args the "old" is, and then
633 # correctly substitute both terms and coefficients.
634 for i
in xrange(l1
-l2
+1):
635 if terms2
==terms1
[i
:i
+l2
]:
636 m1
= Mul(*terms1
[:i
]).subs(old
,new
)
637 m2
= Mul(*terms1
[i
+l2
:]).subs(old
,new
)
638 return Mul(*([coeff1
/coeff2
,m1
,new
,m2
]))
639 return self
.__class
__(*[s
.subs(old
, new
) for s
in self
.args
])
641 def _eval_oseries(self
, order
):
646 #separate terms containing "x" (r) and the rest (l)
652 #if r is empty or just one term, it's easy:
654 if order
.contains(1,x
): return S
.Zero
657 return Mul(*(l
+ [r
[0].oseries(order
)]))
658 #otherwise, we need to calculate how many orders we need to calculate
659 #in each term. Currently this is done using as_leading_term, but this
660 #is fragile and slow, because this involves limits. Let's find some
661 #more clever approach.
662 lt
= [t
.as_leading_term(x
) for t
in r
]
663 for i
in xrange(len(r
)):
664 m
= Mul(*(lt
[:i
]+lt
[i
+1:]))
665 #calculate how many orders we want
667 #expand each term and multiply things together
668 l
.append(r
[i
].oseries(o
))
669 #shouldn't we rather expand everything? This seems to me to leave
670 #things as (x+x**2+...)*(x-x**2+...) etc.:
673 def nseries(self
, x
, x0
, n
):
674 terms
= [t
.nseries(x
, x0
, n
) for t
in self
.args
]
675 return Mul(*terms
).expand()
678 def _eval_as_leading_term(self
, x
):
679 return Mul(*[t
.as_leading_term(x
) for t
in self
.args
])
681 def _eval_conjugate(self
):
682 return Mul(*[t
.conjugate() for t
in self
.args
])
712 import operations
as _