/
expr.py
3164 lines (2590 loc) · 103 KB
/
expr.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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import functools
from collections import defaultdict
from mpmath.libmp import mpf_log, prec_to_dps
from ..utilities import default_sort_key
from .assumptions import ManagedProperties
from .basic import Atom, Basic
from .cache import cacheit
from .compatibility import as_int
from .decorators import _sympifyit, call_highest_priority
from .evalf import EvalfMixin, PrecisionExhausted, pure_complex
from .sympify import sympify
class Expr(Basic, EvalfMixin, metaclass=ManagedProperties):
"""
Base class for algebraic expressions.
Everything that requires arithmetic operations to be defined
should subclass this class, instead of Basic (which should be
used only for argument storage and expression manipulation, i.e.
pattern matching, substitutions, etc).
See Also
========
diofant.core.basic.Basic
"""
def __new__(cls, *args):
obj = Basic.__new__(cls, *args)
obj._assumptions = cls.default_assumptions
return obj
@property
def _diff_wrt(self):
"""Is it allowed to take derivative wrt to this instance.
This determines if it is allowed to take derivatives wrt this object.
Subclasses such as Symbol, Function and Derivative should return True
to enable derivatives wrt them. The implementation in Derivative
separates the Symbol and non-Symbol _diff_wrt=True variables and
temporarily converts the non-Symbol vars in Symbols when performing
the differentiation.
Notes
=====
The expr.subs({yourclass: Symbol}) should be well-defined on a
structural level, or this will lead to inconsistent results.
Examples
========
>>> e = Expr()
>>> e._diff_wrt
False
>>> class MyClass(Expr):
... _diff_wrt = True
...
>>> (2*MyClass()).diff(MyClass())
2
See Also
========
diofant.core.function.Derivative
"""
return False
@cacheit
def sort_key(self, order=None):
"""Return a sort key."""
coeff, expr = self.as_coeff_Mul()
if expr.is_Pow:
expr, exp = expr.base, expr.exp
else:
exp = Integer(1)
if expr.is_Dummy:
args = expr.sort_key(),
elif expr.is_Atom:
args = str(expr),
else:
if expr.is_Add:
args = expr.as_ordered_terms(order=order)
elif expr.is_Mul:
args = expr.as_ordered_factors(order=order)
else:
args = expr.args
args = tuple(default_sort_key(arg, order=order) for arg in args)
args = (len(args), tuple(args))
exp = exp.sort_key(order=order)
return expr.class_key(), args, exp, coeff
# ***************
# * Arithmetics *
# ***************
# Expr and its sublcasses use _op_priority to determine which object
# passed to a binary special method (__mul__, etc.) will handle the
# operation. In general, the 'call_highest_priority' decorator will choose
# the object with the highest _op_priority to handle the call.
# Custom subclasses that want to define their own binary special methods
# should set an _op_priority value that is higher than the default.
#
# **NOTE**:
# This is a temporary fix, and will eventually be replaced with
# something better and more powerful. See issue sympy/sympy#5510.
_op_priority = 10.0
def __pos__(self):
return self
def __neg__(self):
return Mul(-1, self)
def __abs__(self):
from ..functions import Abs
return Abs(self)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__radd__')
def __add__(self, other):
return Add(self, other)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__add__')
def __radd__(self, other):
return Add(other, self)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__rsub__')
def __sub__(self, other):
return Add(self, -other)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__sub__')
def __rsub__(self, other):
return Add(other, -self)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__rmul__')
def __mul__(self, other):
return Mul(self, other)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__mul__')
def __rmul__(self, other):
return Mul(other, self)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__rpow__')
def __pow__(self, other):
return Pow(self, other)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__pow__')
def __rpow__(self, other):
return Pow(other, self)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__rtruediv__')
def __truediv__(self, other):
return Mul(self, Pow(other, -1))
@_sympifyit('other', NotImplemented)
@call_highest_priority('__truediv__')
def __rtruediv__(self, other):
return Mul(other, Pow(self, -1))
@_sympifyit('other', NotImplemented)
@call_highest_priority('__rmod__')
def __mod__(self, other):
return Mod(self, other)
@_sympifyit('other', NotImplemented)
@call_highest_priority('__mod__')
def __rmod__(self, other):
return Mod(other, self)
def __int__(self):
r = self.round(2)
if not r.is_Number:
raise TypeError("can't convert complex to int")
if r in (nan, oo, -oo):
raise TypeError(f"can't convert {r} to int")
return int(r)
def __floor__(self):
from ..functions import floor
return floor(self)
def __float__(self):
# Don't bother testing if it's a number; if it's not this is going
# to fail, and if it is we still need to check that it evalf'ed to
# a number.
result = self.evalf(strict=False)
if result.is_Number:
return float(result)
if result.is_number and result.as_real_imag()[1]:
raise TypeError("can't convert complex to float")
raise TypeError("can't convert expression to float")
def __complex__(self):
result = self.evalf(strict=False)
re, im = result.as_real_imag()
return complex(float(re), float(im))
@_sympifyit('other', NotImplemented)
def __ge__(self, other):
from .relational import GreaterThan
for me in (self, other):
if me.is_commutative and me.is_extended_real is False:
raise TypeError(f'Invalid comparison of complex {me}')
if me is nan:
raise TypeError('Invalid NaN comparison')
if self.is_extended_real or other.is_extended_real:
dif = self - other
if dif.is_nonnegative is not None and \
dif.is_nonnegative is not dif.is_negative:
return sympify(dif.is_nonnegative)
return GreaterThan(self, other, evaluate=False)
@_sympifyit('other', NotImplemented)
def __le__(self, other):
from .relational import LessThan
for me in (self, other):
if me.is_commutative and me.is_extended_real is False:
raise TypeError(f'Invalid comparison of complex {me}')
if me is nan:
raise TypeError('Invalid NaN comparison')
if self.is_extended_real or other.is_extended_real:
dif = self - other
if dif.is_nonpositive is not None and \
dif.is_nonpositive is not dif.is_positive:
return sympify(dif.is_nonpositive)
return LessThan(self, other, evaluate=False)
@_sympifyit('other', NotImplemented)
def __gt__(self, other):
from .relational import StrictGreaterThan
for me in (self, other):
if me.is_commutative and me.is_extended_real is False:
raise TypeError(f'Invalid comparison of complex {me}')
if me is nan:
raise TypeError('Invalid NaN comparison')
if self.is_extended_real or other.is_extended_real:
dif = self - other
if dif.is_positive is not None and \
dif.is_positive is not dif.is_nonpositive:
return sympify(dif.is_positive)
return StrictGreaterThan(self, other, evaluate=False)
@_sympifyit('other', NotImplemented)
def __lt__(self, other):
from .relational import StrictLessThan
for me in (self, other):
if me.is_commutative and me.is_extended_real is False:
raise TypeError(f'Invalid comparison of complex {me}')
if me is nan:
raise TypeError('Invalid NaN comparison')
if self.is_extended_real or other.is_extended_real:
dif = self - other
if dif.is_negative is not None and \
dif.is_negative is not dif.is_nonnegative:
return sympify(dif.is_negative)
return StrictLessThan(self, other, evaluate=False)
@staticmethod
def _from_mpmath(x, prec):
from .numbers import Float
if hasattr(x, '_mpf_'):
return Float._new(x._mpf_, prec)
if hasattr(x, '_mpc_'):
re, im = x._mpc_
re = Float._new(re, prec)
im = Float._new(im, prec)*I
return re + im
raise TypeError('expected mpmath number (mpf or mpc)')
@property
def is_number(self):
"""Returns True if 'self' has no free symbols.
It will be faster than ``if not self.free_symbols``, however, since
``is_number`` will fail as soon as it hits a free symbol.
Examples
========
>>> x.is_number
False
>>> (2*x).is_number
False
>>> (2 + log(2)).is_number
True
>>> (2 + Integral(2, x)).is_number
False
>>> (2 + Integral(2, (x, 1, 2))).is_number
True
"""
return all(obj.is_number for obj in self.args)
def _random(self, n=None, re_min=-1, im_min=-1, re_max=1, im_max=1):
"""Return self evaluated, if possible, replacing free symbols with
random complex values, if necessary.
The random complex value for each free symbol is generated
by the random_complex_number routine giving real and imaginary
parts in the range given by the re_min, re_max, im_min, and im_max
values. The returned value is evaluated to a precision of n
(if given) else the maximum of 15 and the precision needed
to get more than 1 digit of precision. If the expression
could not be evaluated to a number, or could not be evaluated
to more than 1 digit of precision, then None is returned.
Examples
========
>>> x._random()
0.688843703050096 + 0.515908805880605*I
>>> sqrt(2)._random(2)
1.4
See Also
========
diofant.utilities.randtest.random_complex_number
"""
free = self.free_symbols
prec = 1
if free:
from ..utilities.randtest import random_complex_number
a, c, b, d = re_min, re_max, im_min, im_max
reps = dict(zip(free, [random_complex_number(a, b, c, d, rational=True)
for zi in free]))
try:
nmag = abs(self.evalf(2, subs=reps, strict=False))
except (ValueError, TypeError):
# if an out of range value resulted in evalf problems
# then return None -- XXX is there a way to know how to
# select a good random number for a given expression?
# e.g. when calculating n! negative values for n should not
# be used
return
else:
reps = {}
nmag = abs(self.evalf(2, strict=False))
if not hasattr(nmag, '_prec'):
# e.g. exp_polar(2*I*pi) doesn't evaluate but is_number is True
return
if nmag._prec != 1:
if n is None:
n = max(prec, 15)
return self.evalf(n, strict=False, subs=reps)
def is_constant(self, *wrt, **flags):
"""Return True if self is constant, False if not, or None if
the constancy could not be determined conclusively.
If an expression has no free symbols then it is a constant. If
there are free symbols it is possible that the expression is a
constant, perhaps (but not necessarily) zero. To test such
expressions, two strategies are tried:
1) numerical evaluation at two random points. If two such evaluations
give two different values and the values have a precision greater than
1 then self is not constant. If the evaluations agree or could not be
obtained with any precision, no decision is made. The numerical testing
is done only if ``wrt`` is different than the free symbols.
2) differentiation with respect to variables in 'wrt' (or all free
symbols if omitted) to see if the expression is constant or not. This
will not always lead to an expression that is zero even though an
expression is constant (see added test in test_expr.py). If
all derivatives are zero then self is constant with respect to the
given symbols.
If neither evaluation nor differentiation can prove the expression is
constant, None is returned unless two numerical values happened to be
the same and the flag ``failing_number`` is True -- in that case the
numerical value will be returned.
If flag simplify=False is passed, self will not be simplified;
the default is True since self should be simplified before testing.
Examples
========
>>> x.is_constant()
False
>>> Integer(2).is_constant()
True
>>> Sum(x, (x, 1, 10)).is_constant()
True
>>> Sum(x, (x, 1, n)).is_constant()
False
>>> Sum(x, (x, 1, n)).is_constant(y)
True
>>> Sum(x, (x, 1, n)).is_constant(n)
False
>>> Sum(x, (x, 1, n)).is_constant(x)
True
>>> eq = a*cos(x)**2 + a*sin(x)**2 - a
>>> eq.is_constant()
True
>>> eq.subs({x: pi, a: 2}) == eq.subs({x: pi, a: 3}) == 0
True
>>> (0**x).is_constant()
False
>>> x.is_constant()
False
>>> (x**x).is_constant()
False
>>> one = cos(x)**2 + sin(x)**2
>>> one.is_constant()
True
>>> ((one - 1)**(x + 1)).is_constant() in (True, False) # could be 0 or 1
True
"""
from ..functions import Piecewise
simplify = flags.get('simplify', True)
# Except for expressions that contain units, only one of these should
# be necessary since if something is
# known to be a number it should also know that there are no
# free symbols. But is_number quits as soon as it hits a non-number
# whereas free_symbols goes until all free symbols have been collected,
# thus is_number should be faster. But a double check on free symbols
# is made just in case there is a discrepancy between the two.
free = self.free_symbols
if self.is_number or not free:
# if the following assertion fails then that object's free_symbols
# method needs attention: if an expression is a number it cannot
# have free symbols
assert not free
return True
# if we are only interested in some symbols and they are not in the
# free symbols then this expression is constant wrt those symbols
wrt = set(wrt)
if wrt and not wrt & free:
return True
wrt = wrt or free
# simplify unless this has already been done
expr = self
if simplify:
expr = expr.simplify()
# is_zero should be a quick assumptions check; it can be wrong for
# numbers (see test_is_not_constant test), giving False when it
# shouldn't, but hopefully it will never give True unless it is sure.
if expr.is_zero:
return True
# try numerical evaluation to see if we get two different values
failing_number = None
if wrt == free:
# try 0 (for a) and 1 (for b)
try:
a = expr.subs(list(zip(free, [0]*len(free))),
simultaneous=True).evalf(15, strict=False)
if a is nan:
# evaluation may succeed when substitution fails
a = expr._random(None, 0, 0, 0, 0)
if a is None or a is nan:
# try random real
a = expr._random(None, -1, 0, 1, 0)
except (ZeroDivisionError, TypeError):
a = None
if a is not None and a is not nan:
try:
b = expr.subs(list(zip(free, [1]*len(free))),
simultaneous=True).evalf(15, strict=False)
if b is nan:
# evaluation may succeed when substitution fails
b = expr._random(None, 1, 0, 1, 0)
except (ZeroDivisionError, TypeError):
b = None
if b is not None and b is not nan and b.equals(a) is False:
return False
# try random real
b = expr._random(None, -1, 0, 1, 0)
if b is not None and b is not nan and b.equals(a) is False:
return False
failing_number = a if a.is_number else b
# now we will test each wrt symbol (or all free symbols) to see if the
# expression depends on them or not using differentiation. This is
# not sufficient for all expressions, however, so we don't return
# False if we get a derivative other than 0 with free symbols.
for w in wrt:
deriv = expr.diff(w)
if simplify:
deriv = deriv.simplify()
if deriv:
if not (deriv.is_Number or pure_complex(deriv)):
if flags.get('failing_number', False):
return failing_number
assert deriv.free_symbols
return # dead line provided _random returns None in such cases
return False
if not expr.has(Piecewise):
return True
def equals(self, other, failing_expression=False):
"""Return True if self == other, False if it doesn't, or None. If
failing_expression is True then the expression which did not simplify
to a 0 will be returned instead of None.
If ``self`` is a Number (or complex number) that is not zero, then
the result is False.
If ``self`` is a number and has not evaluated to zero, evalf will be
used to test whether the expression evaluates to zero. If it does so
and the result has significance (i.e. the precision is either -1, for
a Rational result, or is greater than 1) then the evalf value will be
used to return True or False.
"""
from ..calculus import Order
from .exprtools import factor_terms
other = sympify(other)
if self == other:
return True
# they aren't the same so see if we can make the difference 0;
# don't worry about doing simplification steps one at a time
# because if the expression ever goes to 0 then the subsequent
# simplification steps that are done will be very fast.
diff = self - other
try:
diff = factor_terms(diff.simplify(), radical=True)
except PrecisionExhausted:
pass
if not diff:
return True
if not diff.has(Add, Mod) or diff.has(Order):
# if there is no expanding to be done after simplifying
# then this can't be a zero
return False
constant = diff.is_constant(simplify=False, failing_number=True)
if constant is False:
return False
if constant is None:
# e.g. unless the right simplification is done, a symbolic
# zero is possible (see expression of issue sympy/sympy#6829: without
# simplification constant will be None).
return
ndiff = diff._random()
if ndiff:
return False
if diff.is_zero:
return True
if failing_expression:
return diff
def _eval_is_zero(self):
from ..polys.numberfields import minimal_polynomial
from .function import Function, count_ops
if self.is_number:
try:
# check to see that we can get a value
n2 = self._eval_evalf(2) # pylint: disable=assignment-from-none
if n2 is None or n2._prec == 1:
raise AttributeError
if n2 == nan:
raise AttributeError
except (AttributeError, ValueError, ZeroDivisionError):
return
r, i = self.evalf(2, strict=False).as_real_imag()
if r.is_Number and i.is_Number and r._prec != 1 and i._prec != 1:
if r != 0 or i != 0:
return False
elif (r._prec == 1 and (not i or i._prec == 1) and
self.is_algebraic and not self.has(Function)):
if count_ops(self) > 75:
return
try:
return not minimal_polynomial(self)(0)
except NotImplementedError:
return
def _eval_is_positive(self):
if self.is_number:
if self.is_extended_real is False:
return False
try:
# check to see that we can get a value
n2 = self._eval_evalf(2) # pylint: disable=assignment-from-none
if n2 is None or n2._prec == 1:
raise AttributeError
if n2 == nan:
raise AttributeError
except (AttributeError, ValueError, ZeroDivisionError):
return
r, i = self.evalf(2, strict=False).as_real_imag()
if r.is_Number and i.is_Number and r._prec != 1 and i._prec != 1:
return bool(not i and r > 0)
def _eval_is_negative(self):
if self.is_number:
if self.is_extended_real is False:
return False
try:
# check to see that we can get a value
n2 = self._eval_evalf(2) # pylint: disable=assignment-from-none
if n2 is None or n2._prec == 1:
raise AttributeError
if n2 == nan:
raise AttributeError
except (AttributeError, ValueError, ZeroDivisionError):
return
r, i = self.evalf(2, strict=False).as_real_imag()
if r.is_Number and i.is_Number and r._prec != 1 and i._prec != 1:
return bool(not i and r < 0)
def _eval_interval(self, x, a, b):
"""Returns evaluation over an interval.
For most functions this is: self.subs({x: b}) - self.subs({x: a}),
possibly using limit() if NaN is returned from subs.
If b or a is None, it only evaluates -self.subs({x: a}) or self.subs({b: x}),
respectively.
"""
from ..calculus import Limit, limit
from ..logic import false
if (a is None and b is None):
raise ValueError('Both interval ends cannot be None.')
if a is None:
A = 0
else:
A = self.subs({x: a})
if A.has(nan, oo, -oo, zoo):
A = limit(self, x, a, -1 if (a < b) is not false else 1)
if isinstance(A, Limit):
raise NotImplementedError('Could not compute limit')
if b is None:
B = 0
else:
B = self.subs({x: b})
if B.has(nan, oo, -oo, zoo):
B = limit(self, x, b, 1 if (a < b) is not false else -1)
if isinstance(B, Limit):
raise NotImplementedError('Could not compute limit')
return B - A
def _eval_power(self, other):
# subclass to compute self**other for cases when
# other is not NaN, 0, or 1
return
def _eval_conjugate(self):
if self.is_extended_real:
return self
if self.is_imaginary:
return -self
def conjugate(self):
"""Returns the complex conjugate of self.
See Also
========
diofant.functions.elementary.complexes.conjugate
"""
from ..functions.elementary.complexes import conjugate as c
return c(self)
def _eval_transpose(self):
if self.is_complex or self.is_extended_real:
return self
def transpose(self):
"""Transpose self.
See Also
========
diofant.functions.elementary.complexes.transpose
"""
from ..functions.elementary.complexes import transpose
return transpose(self)
def _eval_adjoint(self):
from ..functions.elementary.complexes import conjugate, transpose
obj = self._eval_conjugate()
if obj is not None:
return transpose(obj)
obj = self._eval_transpose()
if obj is not None:
return conjugate(obj)
def adjoint(self):
"""Compute conjugate transpose or Hermite conjugation.
See Also
========
diofant.functions.elementary.complexes.adjoint
"""
from ..functions.elementary.complexes import adjoint
return adjoint(self)
@classmethod
def _parse_order(cls, order):
"""Parse and configure the ordering of terms."""
from ..polys.orderings import monomial_key
try:
reverse = order.startswith('rev-')
except AttributeError:
reverse = False
else:
if reverse:
order = order[4:]
monom_key = monomial_key(order)
def neg(monom):
result = []
for m in monom:
if isinstance(m, tuple):
result.append(neg(m))
else:
result.append(-m)
return tuple(result)
def key(term):
_, ((re, im), monom, ncpart) = term
monom = neg(monom_key(monom))
ncpart = tuple(e.sort_key(order=order) for e in ncpart)
coeff = ((bool(im), im), (re, im))
return monom, ncpart, coeff
return key, reverse
def as_ordered_factors(self, order=None):
"""Return list of ordered factors (if Mul) else [self]."""
return [self]
def as_ordered_terms(self, order=None, data=False):
"""Transform an expression to an ordered list of terms.
Examples
========
>>> (sin(x)**2*cos(x) + sin(x)**2 + 1).as_ordered_terms()
[sin(x)**2*cos(x), sin(x)**2, 1]
"""
key, reverse = self._parse_order(order)
terms, gens = self.as_terms()
if not any(term.is_Order for term, _ in terms):
ordered = sorted(terms, key=key, reverse=reverse)
else:
_terms, _order = [], []
for term, repr in terms:
if not term.is_Order:
_terms.append((term, repr))
else:
_order.append((term, repr))
ordered = sorted(_terms, key=key, reverse=True) \
+ sorted(_order, key=key, reverse=True)
if data:
return ordered, gens
return [term for term, _ in ordered]
def as_terms(self):
"""Transform an expression to a list of terms."""
from . import Add, Mul
from .exprtools import decompose_power
gens, terms = set(), []
for term in Add.make_args(self):
coeff, _term = term.as_coeff_Mul()
coeff = complex(coeff)
cpart, ncpart = {}, []
if _term is not Integer(1):
for factor in Mul.make_args(_term):
if factor.is_number:
try:
coeff *= complex(factor)
continue
except TypeError:
pass
if factor.is_commutative:
base, exp = decompose_power(factor)
cpart[base] = exp
gens.add(base)
else:
ncpart.append(factor)
coeff = coeff.real, coeff.imag
ncpart = tuple(ncpart)
terms.append((term, (coeff, cpart, ncpart)))
gens = sorted(gens, key=default_sort_key)
k, indices = len(gens), {}
for i, g in enumerate(gens):
indices[g] = i
result = []
for term, (coeff, cpart, ncpart) in terms:
monom = [0]*k
for base, exp in cpart.items():
monom[indices[base]] = exp
result.append((term, (coeff, tuple(monom), ncpart)))
return result, gens
def removeO(self):
"""Removes the additive O(..) symbol if there is one."""
return self
def getO(self):
"""Returns the additive O(..) symbol if there is one, else None."""
return
def getn(self):
"""Returns the order of the expression.
The order is determined either from the O(...) term. If there
is no O(...) term, it returns None.
Examples
========
>>> (1 + x + O(x**2)).getn()
2
>>> (1 + x).getn()
"""
from .symbol import Dummy, Symbol
o = self.getO() # pylint: disable=assignment-from-none
if o is None:
return
if o.is_Order:
o = o.expr
if o is Integer(1):
return Integer(0)
if o.is_Symbol:
return Integer(1)
if o.is_Pow:
return o.args[1]
if o.is_Mul: # x**n*log(x)**n or x**n/log(x)**n
for oi in o.args:
if oi.is_Symbol:
return Integer(1)
if oi.is_Pow:
syms = oi.atoms(Dummy, Symbol)
if len(syms) == 1:
x = syms.pop()
oi = oi.subs({x: Dummy('x', positive=True)})
if oi.base.is_Symbol and oi.exp.is_Rational:
return abs(oi.exp)
raise NotImplementedError(f'not sure of order of {o}')
def count_ops(self, visual=None):
"""Wrapper for count_ops that returns the operation count."""
from .function import count_ops
return count_ops(self, visual)
def args_cnc(self, cset=False, warn=True, split_1=True):
"""Return [commutative factors, non-commutative factors] of self.
self is treated as a Mul and the ordering of the factors is maintained.
If ``cset`` is True the commutative factors will be returned in a set.
If there were repeated factors (as may happen with an unevaluated Mul)
then an error will be raised unless it is explicitly suppressed by
setting ``warn`` to False.
Note: -1 is always separated from a Number unless split_1 is False.
>>> A, B = symbols('A B', commutative=0)
>>> (-2*x*y).args_cnc()
[[-1, 2, x, y], []]
>>> (-2.5*x).args_cnc()
[[-1, 2.5, x], []]
>>> (-2*x*A*B*y).args_cnc()
[[-1, 2, x, y], [A, B]]
>>> (-2*x*A*B*y).args_cnc(split_1=False)
[[-2, x, y], [A, B]]
>>> (-2*x*y).args_cnc(cset=True)
[{-1, 2, x, y}, []]
The arg is always treated as a Mul:
>>> (-2 + x + A).args_cnc()
[[], [x - 2 + A]]
>>> (-oo).args_cnc() # -oo is a singleton
[[-1, oo], []]
"""
if self.is_Mul:
args = list(self.args)
else:
args = [self]
for i, mi in enumerate(args):
if not mi.is_commutative:
c = args[:i]
nc = args[i:]
break
else:
c = args
nc = []
if c and split_1 and (
c[0].is_Number and
c[0].is_negative and
c[0] is not Integer(-1)):
c[:1] = [Integer(-1), -c[0]]
if cset:
clen = len(c)
c = set(c)
if clen and warn and len(c) != clen:
raise ValueError('repeated commutative arguments: '
f'{[ci for ci in c if list(self.args).count(ci) > 1]}')
return [c, nc]
def coeff(self, x, n=1, right=False):
"""Returns the coefficient from the term(s) containing ``x**n``. If ``n``
is zero then all terms independent of ``x`` will be returned.
When ``x`` is noncommutative, the coefficient to the left (default) or
right of ``x`` can be returned. The keyword 'right' is ignored when
``x`` is commutative.
See Also
========
diofant.core.expr.Expr.as_coefficient
diofant.core.expr.Expr.as_coeff_Add
diofant.core.expr.Expr.as_coeff_Mul
diofant.core.expr.Expr.as_independent
diofant.polys.polytools.Poly.coeff_monomial
Examples
========
You can select terms that have an explicit negative in front of them:
>>> (-x + 2*y).coeff(-1)
x
>>> (x - 2*y).coeff(-1)
2*y