Chris@87
|
1 """
|
Chris@87
|
2 Abstract base class for the various polynomial Classes.
|
Chris@87
|
3
|
Chris@87
|
4 The ABCPolyBase class provides the methods needed to implement the common API
|
Chris@87
|
5 for the various polynomial classes. It operates as a mixin, but uses the
|
Chris@87
|
6 abc module from the stdlib, hence it is only available for Python >= 2.6.
|
Chris@87
|
7
|
Chris@87
|
8 """
|
Chris@87
|
9 from __future__ import division, absolute_import, print_function
|
Chris@87
|
10
|
Chris@87
|
11 from abc import ABCMeta, abstractmethod, abstractproperty
|
Chris@87
|
12 from numbers import Number
|
Chris@87
|
13
|
Chris@87
|
14 import numpy as np
|
Chris@87
|
15 from . import polyutils as pu
|
Chris@87
|
16
|
Chris@87
|
17 __all__ = ['ABCPolyBase']
|
Chris@87
|
18
|
Chris@87
|
19 class ABCPolyBase(object):
|
Chris@87
|
20 """An abstract base class for series classes.
|
Chris@87
|
21
|
Chris@87
|
22 ABCPolyBase provides the standard Python numerical methods
|
Chris@87
|
23 '+', '-', '*', '//', '%', 'divmod', '**', and '()' along with the
|
Chris@87
|
24 methods listed below.
|
Chris@87
|
25
|
Chris@87
|
26 .. versionadded:: 1.9.0
|
Chris@87
|
27
|
Chris@87
|
28 Parameters
|
Chris@87
|
29 ----------
|
Chris@87
|
30 coef : array_like
|
Chris@87
|
31 Series coefficients in order of increasing degree, i.e.,
|
Chris@87
|
32 ``(1, 2, 3)`` gives ``1*P_0(x) + 2*P_1(x) + 3*P_2(x)``, where
|
Chris@87
|
33 ``P_i`` is the basis polynomials of degree ``i``.
|
Chris@87
|
34 domain : (2,) array_like, optional
|
Chris@87
|
35 Domain to use. The interval ``[domain[0], domain[1]]`` is mapped
|
Chris@87
|
36 to the interval ``[window[0], window[1]]`` by shifting and scaling.
|
Chris@87
|
37 The default value is the derived class domain.
|
Chris@87
|
38 window : (2,) array_like, optional
|
Chris@87
|
39 Window, see domain for its use. The default value is the
|
Chris@87
|
40 derived class window.
|
Chris@87
|
41
|
Chris@87
|
42 Attributes
|
Chris@87
|
43 ----------
|
Chris@87
|
44 coef : (N,) ndarray
|
Chris@87
|
45 Series coefficients in order of increasing degree.
|
Chris@87
|
46 domain : (2,) ndarray
|
Chris@87
|
47 Domain that is mapped to window.
|
Chris@87
|
48 window : (2,) ndarray
|
Chris@87
|
49 Window that domain is mapped to.
|
Chris@87
|
50
|
Chris@87
|
51 Class Attributes
|
Chris@87
|
52 ----------------
|
Chris@87
|
53 maxpower : int
|
Chris@87
|
54 Maximum power allowed, i.e., the largest number ``n`` such that
|
Chris@87
|
55 ``p(x)**n`` is allowed. This is to limit runaway polynomial size.
|
Chris@87
|
56 domain : (2,) ndarray
|
Chris@87
|
57 Default domain of the class.
|
Chris@87
|
58 window : (2,) ndarray
|
Chris@87
|
59 Default window of the class.
|
Chris@87
|
60
|
Chris@87
|
61 """
|
Chris@87
|
62 __metaclass__ = ABCMeta
|
Chris@87
|
63
|
Chris@87
|
64 # Not hashable
|
Chris@87
|
65 __hash__ = None
|
Chris@87
|
66
|
Chris@87
|
67 # Don't let participate in array operations. Value doesn't matter.
|
Chris@87
|
68 __array_priority__ = 1000
|
Chris@87
|
69
|
Chris@87
|
70 # Limit runaway size. T_n^m has degree n*m
|
Chris@87
|
71 maxpower = 100
|
Chris@87
|
72
|
Chris@87
|
73 @abstractproperty
|
Chris@87
|
74 def domain(self):
|
Chris@87
|
75 pass
|
Chris@87
|
76
|
Chris@87
|
77 @abstractproperty
|
Chris@87
|
78 def window(self):
|
Chris@87
|
79 pass
|
Chris@87
|
80
|
Chris@87
|
81 @abstractproperty
|
Chris@87
|
82 def nickname(self):
|
Chris@87
|
83 pass
|
Chris@87
|
84
|
Chris@87
|
85 @abstractmethod
|
Chris@87
|
86 def _add(self):
|
Chris@87
|
87 pass
|
Chris@87
|
88
|
Chris@87
|
89 @abstractmethod
|
Chris@87
|
90 def _sub(self):
|
Chris@87
|
91 pass
|
Chris@87
|
92
|
Chris@87
|
93 @abstractmethod
|
Chris@87
|
94 def _mul(self):
|
Chris@87
|
95 pass
|
Chris@87
|
96
|
Chris@87
|
97 @abstractmethod
|
Chris@87
|
98 def _div(self):
|
Chris@87
|
99 pass
|
Chris@87
|
100
|
Chris@87
|
101 @abstractmethod
|
Chris@87
|
102 def _pow(self):
|
Chris@87
|
103 pass
|
Chris@87
|
104
|
Chris@87
|
105 @abstractmethod
|
Chris@87
|
106 def _val(self):
|
Chris@87
|
107 pass
|
Chris@87
|
108
|
Chris@87
|
109 @abstractmethod
|
Chris@87
|
110 def _int(self):
|
Chris@87
|
111 pass
|
Chris@87
|
112
|
Chris@87
|
113 @abstractmethod
|
Chris@87
|
114 def _der(self):
|
Chris@87
|
115 pass
|
Chris@87
|
116
|
Chris@87
|
117 @abstractmethod
|
Chris@87
|
118 def _fit(self):
|
Chris@87
|
119 pass
|
Chris@87
|
120
|
Chris@87
|
121 @abstractmethod
|
Chris@87
|
122 def _line(self):
|
Chris@87
|
123 pass
|
Chris@87
|
124
|
Chris@87
|
125 @abstractmethod
|
Chris@87
|
126 def _roots(self):
|
Chris@87
|
127 pass
|
Chris@87
|
128
|
Chris@87
|
129 @abstractmethod
|
Chris@87
|
130 def _fromroots(self):
|
Chris@87
|
131 pass
|
Chris@87
|
132
|
Chris@87
|
133 def has_samecoef(self, other):
|
Chris@87
|
134 """Check if coefficients match.
|
Chris@87
|
135
|
Chris@87
|
136 .. versionadded:: 1.6.0
|
Chris@87
|
137
|
Chris@87
|
138 Parameters
|
Chris@87
|
139 ----------
|
Chris@87
|
140 other : class instance
|
Chris@87
|
141 The other class must have the ``coef`` attribute.
|
Chris@87
|
142
|
Chris@87
|
143 Returns
|
Chris@87
|
144 -------
|
Chris@87
|
145 bool : boolean
|
Chris@87
|
146 True if the coefficients are the same, False otherwise.
|
Chris@87
|
147
|
Chris@87
|
148 """
|
Chris@87
|
149 if len(self.coef) != len(other.coef):
|
Chris@87
|
150 return False
|
Chris@87
|
151 elif not np.all(self.coef == other.coef):
|
Chris@87
|
152 return False
|
Chris@87
|
153 else:
|
Chris@87
|
154 return True
|
Chris@87
|
155
|
Chris@87
|
156 def has_samedomain(self, other):
|
Chris@87
|
157 """Check if domains match.
|
Chris@87
|
158
|
Chris@87
|
159 .. versionadded:: 1.6.0
|
Chris@87
|
160
|
Chris@87
|
161 Parameters
|
Chris@87
|
162 ----------
|
Chris@87
|
163 other : class instance
|
Chris@87
|
164 The other class must have the ``domain`` attribute.
|
Chris@87
|
165
|
Chris@87
|
166 Returns
|
Chris@87
|
167 -------
|
Chris@87
|
168 bool : boolean
|
Chris@87
|
169 True if the domains are the same, False otherwise.
|
Chris@87
|
170
|
Chris@87
|
171 """
|
Chris@87
|
172 return np.all(self.domain == other.domain)
|
Chris@87
|
173
|
Chris@87
|
174 def has_samewindow(self, other):
|
Chris@87
|
175 """Check if windows match.
|
Chris@87
|
176
|
Chris@87
|
177 .. versionadded:: 1.6.0
|
Chris@87
|
178
|
Chris@87
|
179 Parameters
|
Chris@87
|
180 ----------
|
Chris@87
|
181 other : class instance
|
Chris@87
|
182 The other class must have the ``window`` attribute.
|
Chris@87
|
183
|
Chris@87
|
184 Returns
|
Chris@87
|
185 -------
|
Chris@87
|
186 bool : boolean
|
Chris@87
|
187 True if the windows are the same, False otherwise.
|
Chris@87
|
188
|
Chris@87
|
189 """
|
Chris@87
|
190 return np.all(self.window == other.window)
|
Chris@87
|
191
|
Chris@87
|
192 def has_sametype(self, other):
|
Chris@87
|
193 """Check if types match.
|
Chris@87
|
194
|
Chris@87
|
195 .. versionadded:: 1.7.0
|
Chris@87
|
196
|
Chris@87
|
197 Parameters
|
Chris@87
|
198 ----------
|
Chris@87
|
199 other : object
|
Chris@87
|
200 Class instance.
|
Chris@87
|
201
|
Chris@87
|
202 Returns
|
Chris@87
|
203 -------
|
Chris@87
|
204 bool : boolean
|
Chris@87
|
205 True if other is same class as self
|
Chris@87
|
206
|
Chris@87
|
207 """
|
Chris@87
|
208 return isinstance(other, self.__class__)
|
Chris@87
|
209
|
Chris@87
|
210 def _get_coefficients(self, other):
|
Chris@87
|
211 """Interpret other as polynomial coefficients.
|
Chris@87
|
212
|
Chris@87
|
213 The `other` argument is checked to see if it is of the same
|
Chris@87
|
214 class as self with identical domain and window. If so,
|
Chris@87
|
215 return its coefficients, otherwise return `other`.
|
Chris@87
|
216
|
Chris@87
|
217 .. versionadded:: 1.9.0
|
Chris@87
|
218
|
Chris@87
|
219 Parameters
|
Chris@87
|
220 ----------
|
Chris@87
|
221 other : anything
|
Chris@87
|
222 Object to be checked.
|
Chris@87
|
223
|
Chris@87
|
224 Returns
|
Chris@87
|
225 -------
|
Chris@87
|
226 coef:
|
Chris@87
|
227 The coefficients of`other` if it is a compatible instance,
|
Chris@87
|
228 of ABCPolyBase, otherwise `other`.
|
Chris@87
|
229
|
Chris@87
|
230 Raises
|
Chris@87
|
231 ------
|
Chris@87
|
232 TypeError:
|
Chris@87
|
233 When `other` is an incompatible instance of ABCPolyBase.
|
Chris@87
|
234
|
Chris@87
|
235 """
|
Chris@87
|
236 if isinstance(other, ABCPolyBase):
|
Chris@87
|
237 if not isinstance(other, self.__class__):
|
Chris@87
|
238 raise TypeError("Polynomial types differ")
|
Chris@87
|
239 elif not np.all(self.domain == other.domain):
|
Chris@87
|
240 raise TypeError("Domains differ")
|
Chris@87
|
241 elif not np.all(self.window == other.window):
|
Chris@87
|
242 raise TypeError("Windows differ")
|
Chris@87
|
243 return other.coef
|
Chris@87
|
244 return other
|
Chris@87
|
245
|
Chris@87
|
246 def __init__(self, coef, domain=None, window=None):
|
Chris@87
|
247 [coef] = pu.as_series([coef], trim=False)
|
Chris@87
|
248 self.coef = coef
|
Chris@87
|
249
|
Chris@87
|
250 if domain is not None:
|
Chris@87
|
251 [domain] = pu.as_series([domain], trim=False)
|
Chris@87
|
252 if len(domain) != 2:
|
Chris@87
|
253 raise ValueError("Domain has wrong number of elements.")
|
Chris@87
|
254 self.domain = domain
|
Chris@87
|
255
|
Chris@87
|
256 if window is not None:
|
Chris@87
|
257 [window] = pu.as_series([window], trim=False)
|
Chris@87
|
258 if len(window) != 2:
|
Chris@87
|
259 raise ValueError("Window has wrong number of elements.")
|
Chris@87
|
260 self.window = window
|
Chris@87
|
261
|
Chris@87
|
262 def __repr__(self):
|
Chris@87
|
263 format = "%s(%s, %s, %s)"
|
Chris@87
|
264 coef = repr(self.coef)[6:-1]
|
Chris@87
|
265 domain = repr(self.domain)[6:-1]
|
Chris@87
|
266 window = repr(self.window)[6:-1]
|
Chris@87
|
267 name = self.__class__.__name__
|
Chris@87
|
268 return format % (name, coef, domain, window)
|
Chris@87
|
269
|
Chris@87
|
270 def __str__(self):
|
Chris@87
|
271 format = "%s(%s)"
|
Chris@87
|
272 coef = str(self.coef)
|
Chris@87
|
273 name = self.nickname
|
Chris@87
|
274 return format % (name, coef)
|
Chris@87
|
275
|
Chris@87
|
276 # Pickle and copy
|
Chris@87
|
277
|
Chris@87
|
278 def __getstate__(self):
|
Chris@87
|
279 ret = self.__dict__.copy()
|
Chris@87
|
280 ret['coef'] = self.coef.copy()
|
Chris@87
|
281 ret['domain'] = self.domain.copy()
|
Chris@87
|
282 ret['window'] = self.window.copy()
|
Chris@87
|
283 return ret
|
Chris@87
|
284
|
Chris@87
|
285 def __setstate__(self, dict):
|
Chris@87
|
286 self.__dict__ = dict
|
Chris@87
|
287
|
Chris@87
|
288 # Call
|
Chris@87
|
289
|
Chris@87
|
290 def __call__(self, arg):
|
Chris@87
|
291 off, scl = pu.mapparms(self.domain, self.window)
|
Chris@87
|
292 arg = off + scl*arg
|
Chris@87
|
293 return self._val(arg, self.coef)
|
Chris@87
|
294
|
Chris@87
|
295 def __iter__(self):
|
Chris@87
|
296 return iter(self.coef)
|
Chris@87
|
297
|
Chris@87
|
298 def __len__(self):
|
Chris@87
|
299 return len(self.coef)
|
Chris@87
|
300
|
Chris@87
|
301 # Numeric properties.
|
Chris@87
|
302
|
Chris@87
|
303 def __neg__(self):
|
Chris@87
|
304 return self.__class__(-self.coef, self.domain, self.window)
|
Chris@87
|
305
|
Chris@87
|
306 def __pos__(self):
|
Chris@87
|
307 return self
|
Chris@87
|
308
|
Chris@87
|
309 def __add__(self, other):
|
Chris@87
|
310 try:
|
Chris@87
|
311 othercoef = self._get_coefficients(other)
|
Chris@87
|
312 coef = self._add(self.coef, othercoef)
|
Chris@87
|
313 except TypeError as e:
|
Chris@87
|
314 raise e
|
Chris@87
|
315 except:
|
Chris@87
|
316 return NotImplemented
|
Chris@87
|
317 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
318
|
Chris@87
|
319 def __sub__(self, other):
|
Chris@87
|
320 try:
|
Chris@87
|
321 othercoef = self._get_coefficients(other)
|
Chris@87
|
322 coef = self._sub(self.coef, othercoef)
|
Chris@87
|
323 except TypeError as e:
|
Chris@87
|
324 raise e
|
Chris@87
|
325 except:
|
Chris@87
|
326 return NotImplemented
|
Chris@87
|
327 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
328
|
Chris@87
|
329 def __mul__(self, other):
|
Chris@87
|
330 try:
|
Chris@87
|
331 othercoef = self._get_coefficients(other)
|
Chris@87
|
332 coef = self._mul(self.coef, othercoef)
|
Chris@87
|
333 except TypeError as e:
|
Chris@87
|
334 raise e
|
Chris@87
|
335 except:
|
Chris@87
|
336 return NotImplemented
|
Chris@87
|
337 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
338
|
Chris@87
|
339 def __div__(self, other):
|
Chris@87
|
340 # set to __floordiv__, /, for now.
|
Chris@87
|
341 return self.__floordiv__(other)
|
Chris@87
|
342
|
Chris@87
|
343 def __truediv__(self, other):
|
Chris@87
|
344 # there is no true divide if the rhs is not a Number, although it
|
Chris@87
|
345 # could return the first n elements of an infinite series.
|
Chris@87
|
346 # It is hard to see where n would come from, though.
|
Chris@87
|
347 if not isinstance(other, Number) or isinstance(other, bool):
|
Chris@87
|
348 form = "unsupported types for true division: '%s', '%s'"
|
Chris@87
|
349 raise TypeError(form % (type(self), type(other)))
|
Chris@87
|
350 return self.__floordiv__(other)
|
Chris@87
|
351
|
Chris@87
|
352 def __floordiv__(self, other):
|
Chris@87
|
353 res = self.__divmod__(other)
|
Chris@87
|
354 if res is NotImplemented:
|
Chris@87
|
355 return res
|
Chris@87
|
356 return res[0]
|
Chris@87
|
357
|
Chris@87
|
358 def __mod__(self, other):
|
Chris@87
|
359 res = self.__divmod__(other)
|
Chris@87
|
360 if res is NotImplemented:
|
Chris@87
|
361 return res
|
Chris@87
|
362 return res[1]
|
Chris@87
|
363
|
Chris@87
|
364 def __divmod__(self, other):
|
Chris@87
|
365 try:
|
Chris@87
|
366 othercoef = self._get_coefficients(other)
|
Chris@87
|
367 quo, rem = self._div(self.coef, othercoef)
|
Chris@87
|
368 except (TypeError, ZeroDivisionError) as e:
|
Chris@87
|
369 raise e
|
Chris@87
|
370 except:
|
Chris@87
|
371 return NotImplemented
|
Chris@87
|
372 quo = self.__class__(quo, self.domain, self.window)
|
Chris@87
|
373 rem = self.__class__(rem, self.domain, self.window)
|
Chris@87
|
374 return quo, rem
|
Chris@87
|
375
|
Chris@87
|
376 def __pow__(self, other):
|
Chris@87
|
377 coef = self._pow(self.coef, other, maxpower=self.maxpower)
|
Chris@87
|
378 res = self.__class__(coef, self.domain, self.window)
|
Chris@87
|
379 return res
|
Chris@87
|
380
|
Chris@87
|
381 def __radd__(self, other):
|
Chris@87
|
382 try:
|
Chris@87
|
383 coef = self._add(other, self.coef)
|
Chris@87
|
384 except:
|
Chris@87
|
385 return NotImplemented
|
Chris@87
|
386 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
387
|
Chris@87
|
388 def __rsub__(self, other):
|
Chris@87
|
389 try:
|
Chris@87
|
390 coef = self._sub(other, self.coef)
|
Chris@87
|
391 except:
|
Chris@87
|
392 return NotImplemented
|
Chris@87
|
393 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
394
|
Chris@87
|
395 def __rmul__(self, other):
|
Chris@87
|
396 try:
|
Chris@87
|
397 coef = self._mul(other, self.coef)
|
Chris@87
|
398 except:
|
Chris@87
|
399 return NotImplemented
|
Chris@87
|
400 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
401
|
Chris@87
|
402 def __rdiv__(self, other):
|
Chris@87
|
403 # set to __floordiv__ /.
|
Chris@87
|
404 return self.__rfloordiv__(other)
|
Chris@87
|
405
|
Chris@87
|
406 def __rtruediv__(self, other):
|
Chris@87
|
407 # An instance of ABCPolyBase is not considered a
|
Chris@87
|
408 # Number.
|
Chris@87
|
409 return NotImplemented
|
Chris@87
|
410
|
Chris@87
|
411 def __rfloordiv__(self, other):
|
Chris@87
|
412 res = self.__rdivmod__(other)
|
Chris@87
|
413 if res is NotImplemented:
|
Chris@87
|
414 return res
|
Chris@87
|
415 return res[0]
|
Chris@87
|
416
|
Chris@87
|
417 def __rmod__(self, other):
|
Chris@87
|
418 res = self.__rdivmod__(other)
|
Chris@87
|
419 if res is NotImplemented:
|
Chris@87
|
420 return res
|
Chris@87
|
421 return res[1]
|
Chris@87
|
422
|
Chris@87
|
423 def __rdivmod__(self, other):
|
Chris@87
|
424 try:
|
Chris@87
|
425 quo, rem = self._div(other, self.coef)
|
Chris@87
|
426 except ZeroDivisionError as e:
|
Chris@87
|
427 raise e
|
Chris@87
|
428 except:
|
Chris@87
|
429 return NotImplemented
|
Chris@87
|
430 quo = self.__class__(quo, self.domain, self.window)
|
Chris@87
|
431 rem = self.__class__(rem, self.domain, self.window)
|
Chris@87
|
432 return quo, rem
|
Chris@87
|
433
|
Chris@87
|
434 # Enhance me
|
Chris@87
|
435 # some augmented arithmetic operations could be added here
|
Chris@87
|
436
|
Chris@87
|
437 def __eq__(self, other):
|
Chris@87
|
438 res = (isinstance(other, self.__class__) and
|
Chris@87
|
439 np.all(self.domain == other.domain) and
|
Chris@87
|
440 np.all(self.window == other.window) and
|
Chris@87
|
441 (self.coef.shape == other.coef.shape) and
|
Chris@87
|
442 np.all(self.coef == other.coef))
|
Chris@87
|
443 return res
|
Chris@87
|
444
|
Chris@87
|
445 def __ne__(self, other):
|
Chris@87
|
446 return not self.__eq__(other)
|
Chris@87
|
447
|
Chris@87
|
448 #
|
Chris@87
|
449 # Extra methods.
|
Chris@87
|
450 #
|
Chris@87
|
451
|
Chris@87
|
452 def copy(self):
|
Chris@87
|
453 """Return a copy.
|
Chris@87
|
454
|
Chris@87
|
455 Returns
|
Chris@87
|
456 -------
|
Chris@87
|
457 new_series : series
|
Chris@87
|
458 Copy of self.
|
Chris@87
|
459
|
Chris@87
|
460 """
|
Chris@87
|
461 return self.__class__(self.coef, self.domain, self.window)
|
Chris@87
|
462
|
Chris@87
|
463 def degree(self):
|
Chris@87
|
464 """The degree of the series.
|
Chris@87
|
465
|
Chris@87
|
466 .. versionadded:: 1.5.0
|
Chris@87
|
467
|
Chris@87
|
468 Returns
|
Chris@87
|
469 -------
|
Chris@87
|
470 degree : int
|
Chris@87
|
471 Degree of the series, one less than the number of coefficients.
|
Chris@87
|
472
|
Chris@87
|
473 """
|
Chris@87
|
474 return len(self) - 1
|
Chris@87
|
475
|
Chris@87
|
476 def cutdeg(self, deg):
|
Chris@87
|
477 """Truncate series to the given degree.
|
Chris@87
|
478
|
Chris@87
|
479 Reduce the degree of the series to `deg` by discarding the
|
Chris@87
|
480 high order terms. If `deg` is greater than the current degree a
|
Chris@87
|
481 copy of the current series is returned. This can be useful in least
|
Chris@87
|
482 squares where the coefficients of the high degree terms may be very
|
Chris@87
|
483 small.
|
Chris@87
|
484
|
Chris@87
|
485 .. versionadded:: 1.5.0
|
Chris@87
|
486
|
Chris@87
|
487 Parameters
|
Chris@87
|
488 ----------
|
Chris@87
|
489 deg : non-negative int
|
Chris@87
|
490 The series is reduced to degree `deg` by discarding the high
|
Chris@87
|
491 order terms. The value of `deg` must be a non-negative integer.
|
Chris@87
|
492
|
Chris@87
|
493 Returns
|
Chris@87
|
494 -------
|
Chris@87
|
495 new_series : series
|
Chris@87
|
496 New instance of series with reduced degree.
|
Chris@87
|
497
|
Chris@87
|
498 """
|
Chris@87
|
499 return self.truncate(deg + 1)
|
Chris@87
|
500
|
Chris@87
|
501 def trim(self, tol=0):
|
Chris@87
|
502 """Remove trailing coefficients
|
Chris@87
|
503
|
Chris@87
|
504 Remove trailing coefficients until a coefficient is reached whose
|
Chris@87
|
505 absolute value greater than `tol` or the beginning of the series is
|
Chris@87
|
506 reached. If all the coefficients would be removed the series is set
|
Chris@87
|
507 to ``[0]``. A new series instance is returned with the new
|
Chris@87
|
508 coefficients. The current instance remains unchanged.
|
Chris@87
|
509
|
Chris@87
|
510 Parameters
|
Chris@87
|
511 ----------
|
Chris@87
|
512 tol : non-negative number.
|
Chris@87
|
513 All trailing coefficients less than `tol` will be removed.
|
Chris@87
|
514
|
Chris@87
|
515 Returns
|
Chris@87
|
516 -------
|
Chris@87
|
517 new_series : series
|
Chris@87
|
518 Contains the new set of coefficients.
|
Chris@87
|
519
|
Chris@87
|
520 """
|
Chris@87
|
521 coef = pu.trimcoef(self.coef, tol)
|
Chris@87
|
522 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
523
|
Chris@87
|
524 def truncate(self, size):
|
Chris@87
|
525 """Truncate series to length `size`.
|
Chris@87
|
526
|
Chris@87
|
527 Reduce the series to length `size` by discarding the high
|
Chris@87
|
528 degree terms. The value of `size` must be a positive integer. This
|
Chris@87
|
529 can be useful in least squares where the coefficients of the
|
Chris@87
|
530 high degree terms may be very small.
|
Chris@87
|
531
|
Chris@87
|
532 Parameters
|
Chris@87
|
533 ----------
|
Chris@87
|
534 size : positive int
|
Chris@87
|
535 The series is reduced to length `size` by discarding the high
|
Chris@87
|
536 degree terms. The value of `size` must be a positive integer.
|
Chris@87
|
537
|
Chris@87
|
538 Returns
|
Chris@87
|
539 -------
|
Chris@87
|
540 new_series : series
|
Chris@87
|
541 New instance of series with truncated coefficients.
|
Chris@87
|
542
|
Chris@87
|
543 """
|
Chris@87
|
544 isize = int(size)
|
Chris@87
|
545 if isize != size or isize < 1:
|
Chris@87
|
546 raise ValueError("size must be a positive integer")
|
Chris@87
|
547 if isize >= len(self.coef):
|
Chris@87
|
548 coef = self.coef
|
Chris@87
|
549 else:
|
Chris@87
|
550 coef = self.coef[:isize]
|
Chris@87
|
551 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
552
|
Chris@87
|
553 def convert(self, domain=None, kind=None, window=None):
|
Chris@87
|
554 """Convert series to a different kind and/or domain and/or window.
|
Chris@87
|
555
|
Chris@87
|
556 Parameters
|
Chris@87
|
557 ----------
|
Chris@87
|
558 domain : array_like, optional
|
Chris@87
|
559 The domain of the converted series. If the value is None,
|
Chris@87
|
560 the default domain of `kind` is used.
|
Chris@87
|
561 kind : class, optional
|
Chris@87
|
562 The polynomial series type class to which the current instance
|
Chris@87
|
563 should be converted. If kind is None, then the class of the
|
Chris@87
|
564 current instance is used.
|
Chris@87
|
565 window : array_like, optional
|
Chris@87
|
566 The window of the converted series. If the value is None,
|
Chris@87
|
567 the default window of `kind` is used.
|
Chris@87
|
568
|
Chris@87
|
569 Returns
|
Chris@87
|
570 -------
|
Chris@87
|
571 new_series : series
|
Chris@87
|
572 The returned class can be of different type than the current
|
Chris@87
|
573 instance and/or have a different domain and/or different
|
Chris@87
|
574 window.
|
Chris@87
|
575
|
Chris@87
|
576 Notes
|
Chris@87
|
577 -----
|
Chris@87
|
578 Conversion between domains and class types can result in
|
Chris@87
|
579 numerically ill defined series.
|
Chris@87
|
580
|
Chris@87
|
581 Examples
|
Chris@87
|
582 --------
|
Chris@87
|
583
|
Chris@87
|
584 """
|
Chris@87
|
585 if kind is None:
|
Chris@87
|
586 kind = self.__class__
|
Chris@87
|
587 if domain is None:
|
Chris@87
|
588 domain = kind.domain
|
Chris@87
|
589 if window is None:
|
Chris@87
|
590 window = kind.window
|
Chris@87
|
591 return self(kind.identity(domain, window=window))
|
Chris@87
|
592
|
Chris@87
|
593 def mapparms(self):
|
Chris@87
|
594 """Return the mapping parameters.
|
Chris@87
|
595
|
Chris@87
|
596 The returned values define a linear map ``off + scl*x`` that is
|
Chris@87
|
597 applied to the input arguments before the series is evaluated. The
|
Chris@87
|
598 map depends on the ``domain`` and ``window``; if the current
|
Chris@87
|
599 ``domain`` is equal to the ``window`` the resulting map is the
|
Chris@87
|
600 identity. If the coefficients of the series instance are to be
|
Chris@87
|
601 used by themselves outside this class, then the linear function
|
Chris@87
|
602 must be substituted for the ``x`` in the standard representation of
|
Chris@87
|
603 the base polynomials.
|
Chris@87
|
604
|
Chris@87
|
605 Returns
|
Chris@87
|
606 -------
|
Chris@87
|
607 off, scl : float or complex
|
Chris@87
|
608 The mapping function is defined by ``off + scl*x``.
|
Chris@87
|
609
|
Chris@87
|
610 Notes
|
Chris@87
|
611 -----
|
Chris@87
|
612 If the current domain is the interval ``[l1, r1]`` and the window
|
Chris@87
|
613 is ``[l2, r2]``, then the linear mapping function ``L`` is
|
Chris@87
|
614 defined by the equations::
|
Chris@87
|
615
|
Chris@87
|
616 L(l1) = l2
|
Chris@87
|
617 L(r1) = r2
|
Chris@87
|
618
|
Chris@87
|
619 """
|
Chris@87
|
620 return pu.mapparms(self.domain, self.window)
|
Chris@87
|
621
|
Chris@87
|
622 def integ(self, m=1, k=[], lbnd=None):
|
Chris@87
|
623 """Integrate.
|
Chris@87
|
624
|
Chris@87
|
625 Return a series instance that is the definite integral of the
|
Chris@87
|
626 current series.
|
Chris@87
|
627
|
Chris@87
|
628 Parameters
|
Chris@87
|
629 ----------
|
Chris@87
|
630 m : non-negative int
|
Chris@87
|
631 The number of integrations to perform.
|
Chris@87
|
632 k : array_like
|
Chris@87
|
633 Integration constants. The first constant is applied to the
|
Chris@87
|
634 first integration, the second to the second, and so on. The
|
Chris@87
|
635 list of values must less than or equal to `m` in length and any
|
Chris@87
|
636 missing values are set to zero.
|
Chris@87
|
637 lbnd : Scalar
|
Chris@87
|
638 The lower bound of the definite integral.
|
Chris@87
|
639
|
Chris@87
|
640 Returns
|
Chris@87
|
641 -------
|
Chris@87
|
642 new_series : series
|
Chris@87
|
643 A new series representing the integral. The domain is the same
|
Chris@87
|
644 as the domain of the integrated series.
|
Chris@87
|
645
|
Chris@87
|
646 """
|
Chris@87
|
647 off, scl = self.mapparms()
|
Chris@87
|
648 if lbnd is None:
|
Chris@87
|
649 lbnd = 0
|
Chris@87
|
650 else:
|
Chris@87
|
651 lbnd = off + scl*lbnd
|
Chris@87
|
652 coef = self._int(self.coef, m, k, lbnd, 1./scl)
|
Chris@87
|
653 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
654
|
Chris@87
|
655 def deriv(self, m=1):
|
Chris@87
|
656 """Differentiate.
|
Chris@87
|
657
|
Chris@87
|
658 Return a series instance of that is the derivative of the current
|
Chris@87
|
659 series.
|
Chris@87
|
660
|
Chris@87
|
661 Parameters
|
Chris@87
|
662 ----------
|
Chris@87
|
663 m : non-negative int
|
Chris@87
|
664 The number of integrations to perform.
|
Chris@87
|
665
|
Chris@87
|
666 Returns
|
Chris@87
|
667 -------
|
Chris@87
|
668 new_series : series
|
Chris@87
|
669 A new series representing the derivative. The domain is the same
|
Chris@87
|
670 as the domain of the differentiated series.
|
Chris@87
|
671
|
Chris@87
|
672 """
|
Chris@87
|
673 off, scl = self.mapparms()
|
Chris@87
|
674 coef = self._der(self.coef, m, scl)
|
Chris@87
|
675 return self.__class__(coef, self.domain, self.window)
|
Chris@87
|
676
|
Chris@87
|
677 def roots(self):
|
Chris@87
|
678 """Return the roots of the series polynomial.
|
Chris@87
|
679
|
Chris@87
|
680 Compute the roots for the series. Note that the accuracy of the
|
Chris@87
|
681 roots decrease the further outside the domain they lie.
|
Chris@87
|
682
|
Chris@87
|
683 Returns
|
Chris@87
|
684 -------
|
Chris@87
|
685 roots : ndarray
|
Chris@87
|
686 Array containing the roots of the series.
|
Chris@87
|
687
|
Chris@87
|
688 """
|
Chris@87
|
689 roots = self._roots(self.coef)
|
Chris@87
|
690 return pu.mapdomain(roots, self.window, self.domain)
|
Chris@87
|
691
|
Chris@87
|
692 def linspace(self, n=100, domain=None):
|
Chris@87
|
693 """Return x, y values at equally spaced points in domain.
|
Chris@87
|
694
|
Chris@87
|
695 Returns the x, y values at `n` linearly spaced points across the
|
Chris@87
|
696 domain. Here y is the value of the polynomial at the points x. By
|
Chris@87
|
697 default the domain is the same as that of the series instance.
|
Chris@87
|
698 This method is intended mostly as a plotting aid.
|
Chris@87
|
699
|
Chris@87
|
700 .. versionadded:: 1.5.0
|
Chris@87
|
701
|
Chris@87
|
702 Parameters
|
Chris@87
|
703 ----------
|
Chris@87
|
704 n : int, optional
|
Chris@87
|
705 Number of point pairs to return. The default value is 100.
|
Chris@87
|
706 domain : {None, array_like}, optional
|
Chris@87
|
707 If not None, the specified domain is used instead of that of
|
Chris@87
|
708 the calling instance. It should be of the form ``[beg,end]``.
|
Chris@87
|
709 The default is None which case the class domain is used.
|
Chris@87
|
710
|
Chris@87
|
711 Returns
|
Chris@87
|
712 -------
|
Chris@87
|
713 x, y : ndarray
|
Chris@87
|
714 x is equal to linspace(self.domain[0], self.domain[1], n) and
|
Chris@87
|
715 y is the series evaluated at element of x.
|
Chris@87
|
716
|
Chris@87
|
717 """
|
Chris@87
|
718 if domain is None:
|
Chris@87
|
719 domain = self.domain
|
Chris@87
|
720 x = np.linspace(domain[0], domain[1], n)
|
Chris@87
|
721 y = self(x)
|
Chris@87
|
722 return x, y
|
Chris@87
|
723
|
Chris@87
|
724 @classmethod
|
Chris@87
|
725 def fit(cls, x, y, deg, domain=None, rcond=None, full=False, w=None,
|
Chris@87
|
726 window=None):
|
Chris@87
|
727 """Least squares fit to data.
|
Chris@87
|
728
|
Chris@87
|
729 Return a series instance that is the least squares fit to the data
|
Chris@87
|
730 `y` sampled at `x`. The domain of the returned instance can be
|
Chris@87
|
731 specified and this will often result in a superior fit with less
|
Chris@87
|
732 chance of ill conditioning.
|
Chris@87
|
733
|
Chris@87
|
734 Parameters
|
Chris@87
|
735 ----------
|
Chris@87
|
736 x : array_like, shape (M,)
|
Chris@87
|
737 x-coordinates of the M sample points ``(x[i], y[i])``.
|
Chris@87
|
738 y : array_like, shape (M,) or (M, K)
|
Chris@87
|
739 y-coordinates of the sample points. Several data sets of sample
|
Chris@87
|
740 points sharing the same x-coordinates can be fitted at once by
|
Chris@87
|
741 passing in a 2D-array that contains one dataset per column.
|
Chris@87
|
742 deg : int
|
Chris@87
|
743 Degree of the fitting polynomial.
|
Chris@87
|
744 domain : {None, [beg, end], []}, optional
|
Chris@87
|
745 Domain to use for the returned series. If ``None``,
|
Chris@87
|
746 then a minimal domain that covers the points `x` is chosen. If
|
Chris@87
|
747 ``[]`` the class domain is used. The default value was the
|
Chris@87
|
748 class domain in NumPy 1.4 and ``None`` in later versions.
|
Chris@87
|
749 The ``[]`` option was added in numpy 1.5.0.
|
Chris@87
|
750 rcond : float, optional
|
Chris@87
|
751 Relative condition number of the fit. Singular values smaller
|
Chris@87
|
752 than this relative to the largest singular value will be
|
Chris@87
|
753 ignored. The default value is len(x)*eps, where eps is the
|
Chris@87
|
754 relative precision of the float type, about 2e-16 in most
|
Chris@87
|
755 cases.
|
Chris@87
|
756 full : bool, optional
|
Chris@87
|
757 Switch determining nature of return value. When it is False
|
Chris@87
|
758 (the default) just the coefficients are returned, when True
|
Chris@87
|
759 diagnostic information from the singular value decomposition is
|
Chris@87
|
760 also returned.
|
Chris@87
|
761 w : array_like, shape (M,), optional
|
Chris@87
|
762 Weights. If not None the contribution of each point
|
Chris@87
|
763 ``(x[i],y[i])`` to the fit is weighted by `w[i]`. Ideally the
|
Chris@87
|
764 weights are chosen so that the errors of the products
|
Chris@87
|
765 ``w[i]*y[i]`` all have the same variance. The default value is
|
Chris@87
|
766 None.
|
Chris@87
|
767
|
Chris@87
|
768 .. versionadded:: 1.5.0
|
Chris@87
|
769 window : {[beg, end]}, optional
|
Chris@87
|
770 Window to use for the returned series. The default
|
Chris@87
|
771 value is the default class domain
|
Chris@87
|
772
|
Chris@87
|
773 .. versionadded:: 1.6.0
|
Chris@87
|
774
|
Chris@87
|
775 Returns
|
Chris@87
|
776 -------
|
Chris@87
|
777 new_series : series
|
Chris@87
|
778 A series that represents the least squares fit to the data and
|
Chris@87
|
779 has the domain specified in the call.
|
Chris@87
|
780
|
Chris@87
|
781 [resid, rank, sv, rcond] : list
|
Chris@87
|
782 These values are only returned if `full` = True
|
Chris@87
|
783
|
Chris@87
|
784 resid -- sum of squared residuals of the least squares fit
|
Chris@87
|
785 rank -- the numerical rank of the scaled Vandermonde matrix
|
Chris@87
|
786 sv -- singular values of the scaled Vandermonde matrix
|
Chris@87
|
787 rcond -- value of `rcond`.
|
Chris@87
|
788
|
Chris@87
|
789 For more details, see `linalg.lstsq`.
|
Chris@87
|
790
|
Chris@87
|
791 """
|
Chris@87
|
792 if domain is None:
|
Chris@87
|
793 domain = pu.getdomain(x)
|
Chris@87
|
794 elif type(domain) is list and len(domain) == 0:
|
Chris@87
|
795 domain = cls.domain
|
Chris@87
|
796
|
Chris@87
|
797 if window is None:
|
Chris@87
|
798 window = cls.window
|
Chris@87
|
799
|
Chris@87
|
800 xnew = pu.mapdomain(x, domain, window)
|
Chris@87
|
801 res = cls._fit(xnew, y, deg, w=w, rcond=rcond, full=full)
|
Chris@87
|
802 if full:
|
Chris@87
|
803 [coef, status] = res
|
Chris@87
|
804 return cls(coef, domain=domain, window=window), status
|
Chris@87
|
805 else:
|
Chris@87
|
806 coef = res
|
Chris@87
|
807 return cls(coef, domain=domain, window=window)
|
Chris@87
|
808
|
Chris@87
|
809 @classmethod
|
Chris@87
|
810 def fromroots(cls, roots, domain=[], window=None):
|
Chris@87
|
811 """Return series instance that has the specified roots.
|
Chris@87
|
812
|
Chris@87
|
813 Returns a series representing the product
|
Chris@87
|
814 ``(x - r[0])*(x - r[1])*...*(x - r[n-1])``, where ``r`` is a
|
Chris@87
|
815 list of roots.
|
Chris@87
|
816
|
Chris@87
|
817 Parameters
|
Chris@87
|
818 ----------
|
Chris@87
|
819 roots : array_like
|
Chris@87
|
820 List of roots.
|
Chris@87
|
821 domain : {[], None, array_like}, optional
|
Chris@87
|
822 Domain for the resulting series. If None the domain is the
|
Chris@87
|
823 interval from the smallest root to the largest. If [] the
|
Chris@87
|
824 domain is the class domain. The default is [].
|
Chris@87
|
825 window : {None, array_like}, optional
|
Chris@87
|
826 Window for the returned series. If None the class window is
|
Chris@87
|
827 used. The default is None.
|
Chris@87
|
828
|
Chris@87
|
829 Returns
|
Chris@87
|
830 -------
|
Chris@87
|
831 new_series : series
|
Chris@87
|
832 Series with the specified roots.
|
Chris@87
|
833
|
Chris@87
|
834 """
|
Chris@87
|
835 [roots] = pu.as_series([roots], trim=False)
|
Chris@87
|
836 if domain is None:
|
Chris@87
|
837 domain = pu.getdomain(roots)
|
Chris@87
|
838 elif type(domain) is list and len(domain) == 0:
|
Chris@87
|
839 domain = cls.domain
|
Chris@87
|
840
|
Chris@87
|
841 if window is None:
|
Chris@87
|
842 window = cls.window
|
Chris@87
|
843
|
Chris@87
|
844 deg = len(roots)
|
Chris@87
|
845 off, scl = pu.mapparms(domain, window)
|
Chris@87
|
846 rnew = off + scl*roots
|
Chris@87
|
847 coef = cls._fromroots(rnew) / scl**deg
|
Chris@87
|
848 return cls(coef, domain=domain, window=window)
|
Chris@87
|
849
|
Chris@87
|
850 @classmethod
|
Chris@87
|
851 def identity(cls, domain=None, window=None):
|
Chris@87
|
852 """Identity function.
|
Chris@87
|
853
|
Chris@87
|
854 If ``p`` is the returned series, then ``p(x) == x`` for all
|
Chris@87
|
855 values of x.
|
Chris@87
|
856
|
Chris@87
|
857 Parameters
|
Chris@87
|
858 ----------
|
Chris@87
|
859 domain : {None, array_like}, optional
|
Chris@87
|
860 If given, the array must be of the form ``[beg, end]``, where
|
Chris@87
|
861 ``beg`` and ``end`` are the endpoints of the domain. If None is
|
Chris@87
|
862 given then the class domain is used. The default is None.
|
Chris@87
|
863 window : {None, array_like}, optional
|
Chris@87
|
864 If given, the resulting array must be if the form
|
Chris@87
|
865 ``[beg, end]``, where ``beg`` and ``end`` are the endpoints of
|
Chris@87
|
866 the window. If None is given then the class window is used. The
|
Chris@87
|
867 default is None.
|
Chris@87
|
868
|
Chris@87
|
869 Returns
|
Chris@87
|
870 -------
|
Chris@87
|
871 new_series : series
|
Chris@87
|
872 Series of representing the identity.
|
Chris@87
|
873
|
Chris@87
|
874 """
|
Chris@87
|
875 if domain is None:
|
Chris@87
|
876 domain = cls.domain
|
Chris@87
|
877 if window is None:
|
Chris@87
|
878 window = cls.window
|
Chris@87
|
879 off, scl = pu.mapparms(window, domain)
|
Chris@87
|
880 coef = cls._line(off, scl)
|
Chris@87
|
881 return cls(coef, domain, window)
|
Chris@87
|
882
|
Chris@87
|
883 @classmethod
|
Chris@87
|
884 def basis(cls, deg, domain=None, window=None):
|
Chris@87
|
885 """Series basis polynomial of degree `deg`.
|
Chris@87
|
886
|
Chris@87
|
887 Returns the series representing the basis polynomial of degree `deg`.
|
Chris@87
|
888
|
Chris@87
|
889 .. versionadded:: 1.7.0
|
Chris@87
|
890
|
Chris@87
|
891 Parameters
|
Chris@87
|
892 ----------
|
Chris@87
|
893 deg : int
|
Chris@87
|
894 Degree of the basis polynomial for the series. Must be >= 0.
|
Chris@87
|
895 domain : {None, array_like}, optional
|
Chris@87
|
896 If given, the array must be of the form ``[beg, end]``, where
|
Chris@87
|
897 ``beg`` and ``end`` are the endpoints of the domain. If None is
|
Chris@87
|
898 given then the class domain is used. The default is None.
|
Chris@87
|
899 window : {None, array_like}, optional
|
Chris@87
|
900 If given, the resulting array must be if the form
|
Chris@87
|
901 ``[beg, end]``, where ``beg`` and ``end`` are the endpoints of
|
Chris@87
|
902 the window. If None is given then the class window is used. The
|
Chris@87
|
903 default is None.
|
Chris@87
|
904
|
Chris@87
|
905 Returns
|
Chris@87
|
906 -------
|
Chris@87
|
907 new_series : series
|
Chris@87
|
908 A series with the coefficient of the `deg` term set to one and
|
Chris@87
|
909 all others zero.
|
Chris@87
|
910
|
Chris@87
|
911 """
|
Chris@87
|
912 if domain is None:
|
Chris@87
|
913 domain = cls.domain
|
Chris@87
|
914 if window is None:
|
Chris@87
|
915 window = cls.window
|
Chris@87
|
916 ideg = int(deg)
|
Chris@87
|
917
|
Chris@87
|
918 if ideg != deg or ideg < 0:
|
Chris@87
|
919 raise ValueError("deg must be non-negative integer")
|
Chris@87
|
920 return cls([0]*ideg + [1], domain, window)
|
Chris@87
|
921
|
Chris@87
|
922 @classmethod
|
Chris@87
|
923 def cast(cls, series, domain=None, window=None):
|
Chris@87
|
924 """Convert series to series of this class.
|
Chris@87
|
925
|
Chris@87
|
926 The `series` is expected to be an instance of some polynomial
|
Chris@87
|
927 series of one of the types supported by by the numpy.polynomial
|
Chris@87
|
928 module, but could be some other class that supports the convert
|
Chris@87
|
929 method.
|
Chris@87
|
930
|
Chris@87
|
931 .. versionadded:: 1.7.0
|
Chris@87
|
932
|
Chris@87
|
933 Parameters
|
Chris@87
|
934 ----------
|
Chris@87
|
935 series : series
|
Chris@87
|
936 The series instance to be converted.
|
Chris@87
|
937 domain : {None, array_like}, optional
|
Chris@87
|
938 If given, the array must be of the form ``[beg, end]``, where
|
Chris@87
|
939 ``beg`` and ``end`` are the endpoints of the domain. If None is
|
Chris@87
|
940 given then the class domain is used. The default is None.
|
Chris@87
|
941 window : {None, array_like}, optional
|
Chris@87
|
942 If given, the resulting array must be if the form
|
Chris@87
|
943 ``[beg, end]``, where ``beg`` and ``end`` are the endpoints of
|
Chris@87
|
944 the window. If None is given then the class window is used. The
|
Chris@87
|
945 default is None.
|
Chris@87
|
946
|
Chris@87
|
947 Returns
|
Chris@87
|
948 -------
|
Chris@87
|
949 new_series : series
|
Chris@87
|
950 A series of the same kind as the calling class and equal to
|
Chris@87
|
951 `series` when evaluated.
|
Chris@87
|
952
|
Chris@87
|
953 See Also
|
Chris@87
|
954 --------
|
Chris@87
|
955 convert : similar instance method
|
Chris@87
|
956
|
Chris@87
|
957 """
|
Chris@87
|
958 if domain is None:
|
Chris@87
|
959 domain = cls.domain
|
Chris@87
|
960 if window is None:
|
Chris@87
|
961 window = cls.window
|
Chris@87
|
962 return series.convert(domain, cls, window)
|