forked from sympy/sympy
/
order.py
279 lines (223 loc) · 8.06 KB
/
order.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
from sympy.core.basic import Basic, S, C, sympify
from sympy.core import oo, Rational, Pow
from sympy.core.cache import cacheit
class Order(Basic):
"""
Represents O(f(x)) at the point x = 0.
Definition
==========
g(x) = O(f(x)) as x->0 if and only if
|g(x)|<=M|f(x)| near x=0 (1)
for some positive but finite M. An equivalent way of saying (1) is:
lim_{x->0} |g(x)/f(x)| < oo
Let's illustrate it on the following example:
sin x = x - x**3/3! + O(x**5)
where in this case O(x**5) = x**5/5! - x**7/7! + .... and the definition
of O means:
|x**5/5! - x**7/7! + ....| <= M|x**5| near x=0
or equivalently:
lim_{x->0} | (x**5/5! - x**7/7! + ....) / x**5| < oo
which surely is true, because
lim_{x->0} | (x**5/5! - x**7/7! + ....) / x**5| = 1/5!
So intuitively O(x**3) means: all terms x**3, x**4 and
higher. But not x**2, x or 1.
Examples:
=========
>>> from sympy import *
>>> x = Symbol("x")
>>> O(x)
O(x)
>>> O(x)*x
O(x**2)
>>> O(x)-O(x)
O(x)
External links
--------------
U{Big O notation<http://en.wikipedia.org/wiki/Big_O_notation>}
Properties:
===========
g(x) = O(f(x)) as x->0 <-> |g(x)|<=M|f(x)| near x=0 <-> lim_{x->0} |g(x)/f(x)| < oo
g(x,y) = O(f(x,y)) <-> lim_{x,y->0} |g(x,y)/f(x,y)| < oo, we'll assume that limits commute.
Notes:
======
In O(f(x),x) the expression f(x) is assumed to have a leading term.
O(f(x),x) is automatically transformed to O(f(x).as_leading_term(x),x).
O(expr*f(x),x) is O(f(x),x)
O(expr,x) is O(1)
O(0, x) is 0.
Multivariate O is also supported:
O(f(x,y),x,y) is transformed to O(f(x,y).as_leading_term(x,y).as_leading_term(y), x, y)
If O is used with only expression argument then the symbols are
all symbols in the expression.
"""
is_Order = True
__slots__ = []
@cacheit
def __new__(cls, expr, *symbols, **assumptions):
expr = sympify(expr).expand()
if expr is S.NaN:
return S.NaN
if symbols:
symbols = map(sympify, symbols)
else:
symbols = list(expr.atoms(C.Symbol))
symbols.sort(Basic.compare)
if expr.is_Order:
new_symbols = list(expr.symbols)
for s in symbols:
if s not in new_symbols:
new_symbols.append(s)
if len(new_symbols)==len(expr.symbols):
return expr
symbols = new_symbols
elif symbols:
symbol_map = {}
new_symbols = []
for s in symbols:
if isinstance(s, C.Symbol):
new_symbols.append(s)
continue
z = C.Symbol('z',dummy=True)
x1,s1 = s.solve4linearsymbol(z)
expr = expr.subs(x1,s1)
symbol_map[z] = s
new_symbols.append(z)
if symbol_map:
r = Order(expr, *new_symbols, **assumptions)
expr = r.expr.subs(symbol_map)
symbols = []
for s in r.symbols:
if symbol_map.has_key(s):
symbols.append(symbol_map[s])
else:
symbols.append(s)
else:
if expr.is_Add:
lst = expr.extract_leading_order(*symbols)
expr = C.Add(*[f.expr for (e,f) in lst])
else:
expr = expr.as_leading_term(*symbols)
coeff, terms = expr.as_coeff_terms()
if coeff is S.Zero:
return coeff
expr = C.Mul(*[t for t in terms if t.has(*symbols)])
elif expr is not S.Zero:
expr = S.One
if expr is S.Zero:
return expr
# create Order instance:
obj = Basic.__new__(cls, expr, *symbols, **assumptions)
return obj
def _hashable_content(self):
if self.args[0].is_number:
return (self.args[0],)
return self.args
def oseries(self, order):
return self
def _eval_nseries(self, x, x0, n):
return self
@classmethod
def find_limit(cls, f, x):
"""Basically identical to:
return limit(f, x, 0, dir="+")
but first trying some easy cases (like x**2) using heuristics, to avoid
infinite recursion. This is only needed in the Order class and series
expansion (that shouldn't rely on the Gruntz algorithm too much),
that's why find_limit() is defined here.
"""
from sympy import limit, Wild, log
if f.is_Pow:
if f.args[0] == x:
if f.args[1].is_Rational:
if f.args[1] > 0:
return S.Zero
else:
return oo
if f.args[1].is_number:
if f.args[1].evalf() > 0:
return S.Zero
else:
return oo
if f == x:
return S.Zero
p, q = Wild("p"), Wild("q")
r = f.match(x**p * log(x)**q)
if r:
p, q = r[p], r[q]
if q.is_number and p.is_number:
if q > 0:
if p > 0:
return S.Zero
else:
return -oo
elif q < 0:
if p >= 0:
return S.Zero
else:
return -oo
return limit(f, x, 0, dir="+")
@property
def expr(self):
return self._args[0]
@property
def symbols(self):
return self._args[1:]
def _eval_power(b, e):
if e.is_Number:
return Order(b.expr ** e, *b.symbols)
return
def as_expr_symbols(self, order_symbols):
if order_symbols is None:
order_symbols = self.symbols
else:
for s in self.symbols:
if s not in order_symbols:
order_symbols = order_symbols + (s,)
return self.expr, order_symbols
@cacheit
def contains(self, expr):
"""
Return True if expr belongs to Order(self.expr, *self.symbols).
Return False if self belongs to expr.
Return None if the inclusion relation cannot be determined (e.g. when self and
expr have different symbols).
"""
if expr is S.Zero:
return True
if expr is S.NaN:
return False
if expr.is_Order:
if self.symbols and expr.symbols:
common_symbols = tuple([s for s in self.symbols if s in expr.symbols])
elif self.symbols:
common_symbols = self.symbols
else:
common_symbols = expr.symbols
if not common_symbols:
if not (self.symbols or expr.symbols): # O(1),O(1)
return True
return None
r = None
for s in common_symbols:
l = Order.find_limit(self.expr/expr.expr, s) != 0
if r is None:
r = l
else:
if r != l:
return
return r
obj = Order(expr, *self.symbols)
return self.contains(obj)
def _eval_subs(self, old, new):
if self==old:
return new
if isinstance(old, C.Symbol) and old in self.symbols:
i = list(self.symbols).index(old)
if isinstance(new, C.Symbol):
return Order(self.expr._eval_subs(old, new), *(self.symbols[:i]+(new,)+self.symbols[i+1:]))
return Order(self.expr._eval_subs(old, new), *(self.symbols[:i]+self.symbols[i+1:]))
return Order(self.expr._eval_subs(old, new), *self.symbols)
def _sage_(self):
#XXX: SAGE doesn't have Order yet. Let's return 0 instead.
return Rational(0)._sage_()
Basic.singleton['O'] = lambda : Order