| Viewing file:  _polybase.py (38.35 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
"""Abstract base class for the various polynomial Classes.
 
 The ABCPolyBase class provides the methods needed to implement the common API
 for the various polynomial classes. It operates as a mixin, but uses the
 abc module from the stdlib, hence it is only available for Python >= 2.6.
 
 """
 import os
 import abc
 import numbers
 
 import numpy as np
 from . import polyutils as pu
 
 __all__ = ['ABCPolyBase']
 
 class ABCPolyBase(abc.ABC):
 """An abstract base class for immutable series classes.
 
 ABCPolyBase provides the standard Python numerical methods
 '+', '-', '*', '//', '%', 'divmod', '**', and '()' along with the
 methods listed below.
 
 .. versionadded:: 1.9.0
 
 Parameters
 ----------
 coef : array_like
 Series coefficients in order of increasing degree, i.e.,
 ``(1, 2, 3)`` gives ``1*P_0(x) + 2*P_1(x) + 3*P_2(x)``, where
 ``P_i`` is the basis polynomials of degree ``i``.
 domain : (2,) array_like, optional
 Domain to use. The interval ``[domain[0], domain[1]]`` is mapped
 to the interval ``[window[0], window[1]]`` by shifting and scaling.
 The default value is the derived class domain.
 window : (2,) array_like, optional
 Window, see domain for its use. The default value is the
 derived class window.
 symbol : str, optional
 Symbol used to represent the independent variable in string
 representations of the polynomial expression, e.g. for printing.
 The symbol must be a valid Python identifier. Default value is 'x'.
 
 .. versionadded:: 1.24
 
 Attributes
 ----------
 coef : (N,) ndarray
 Series coefficients in order of increasing degree.
 domain : (2,) ndarray
 Domain that is mapped to window.
 window : (2,) ndarray
 Window that domain is mapped to.
 symbol : str
 Symbol representing the independent variable.
 
 Class Attributes
 ----------------
 maxpower : int
 Maximum power allowed, i.e., the largest number ``n`` such that
 ``p(x)**n`` is allowed. This is to limit runaway polynomial size.
 domain : (2,) ndarray
 Default domain of the class.
 window : (2,) ndarray
 Default window of the class.
 
 """
 
 # Not hashable
 __hash__ = None
 
 # Opt out of numpy ufuncs and Python ops with ndarray subclasses.
 __array_ufunc__ = None
 
 # Limit runaway size. T_n^m has degree n*m
 maxpower = 100
 
 # Unicode character mappings for improved __str__
 _superscript_mapping = str.maketrans({
 "0": "⁰",
 "1": "¹",
 "2": "²",
 "3": "³",
 "4": "⁴",
 "5": "⁵",
 "6": "⁶",
 "7": "⁷",
 "8": "⁸",
 "9": "⁹"
 })
 _subscript_mapping = str.maketrans({
 "0": "₀",
 "1": "₁",
 "2": "₂",
 "3": "₃",
 "4": "₄",
 "5": "₅",
 "6": "₆",
 "7": "₇",
 "8": "₈",
 "9": "₉"
 })
 # Some fonts don't support full unicode character ranges necessary for
 # the full set of superscripts and subscripts, including common/default
 # fonts in Windows shells/terminals. Therefore, default to ascii-only
 # printing on windows.
 _use_unicode = not os.name == 'nt'
 
 @property
 def symbol(self):
 return self._symbol
 
 @property
 @abc.abstractmethod
 def domain(self):
 pass
 
 @property
 @abc.abstractmethod
 def window(self):
 pass
 
 @property
 @abc.abstractmethod
 def basis_name(self):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _add(c1, c2):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _sub(c1, c2):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _mul(c1, c2):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _div(c1, c2):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _pow(c, pow, maxpower=None):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _val(x, c):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _int(c, m, k, lbnd, scl):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _der(c, m, scl):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _fit(x, y, deg, rcond, full):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _line(off, scl):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _roots(c):
 pass
 
 @staticmethod
 @abc.abstractmethod
 def _fromroots(r):
 pass
 
 def has_samecoef(self, other):
 """Check if coefficients match.
 
 .. versionadded:: 1.6.0
 
 Parameters
 ----------
 other : class instance
 The other class must have the ``coef`` attribute.
 
 Returns
 -------
 bool : boolean
 True if the coefficients are the same, False otherwise.
 
 """
 if len(self.coef) != len(other.coef):
 return False
 elif not np.all(self.coef == other.coef):
 return False
 else:
 return True
 
 def has_samedomain(self, other):
 """Check if domains match.
 
 .. versionadded:: 1.6.0
 
 Parameters
 ----------
 other : class instance
 The other class must have the ``domain`` attribute.
 
 Returns
 -------
 bool : boolean
 True if the domains are the same, False otherwise.
 
 """
 return np.all(self.domain == other.domain)
 
 def has_samewindow(self, other):
 """Check if windows match.
 
 .. versionadded:: 1.6.0
 
 Parameters
 ----------
 other : class instance
 The other class must have the ``window`` attribute.
 
 Returns
 -------
 bool : boolean
 True if the windows are the same, False otherwise.
 
 """
 return np.all(self.window == other.window)
 
 def has_sametype(self, other):
 """Check if types match.
 
 .. versionadded:: 1.7.0
 
 Parameters
 ----------
 other : object
 Class instance.
 
 Returns
 -------
 bool : boolean
 True if other is same class as self
 
 """
 return isinstance(other, self.__class__)
 
 def _get_coefficients(self, other):
 """Interpret other as polynomial coefficients.
 
 The `other` argument is checked to see if it is of the same
 class as self with identical domain and window. If so,
 return its coefficients, otherwise return `other`.
 
 .. versionadded:: 1.9.0
 
 Parameters
 ----------
 other : anything
 Object to be checked.
 
 Returns
 -------
 coef
 The coefficients of`other` if it is a compatible instance,
 of ABCPolyBase, otherwise `other`.
 
 Raises
 ------
 TypeError
 When `other` is an incompatible instance of ABCPolyBase.
 
 """
 if isinstance(other, ABCPolyBase):
 if not isinstance(other, self.__class__):
 raise TypeError("Polynomial types differ")
 elif not np.all(self.domain == other.domain):
 raise TypeError("Domains differ")
 elif not np.all(self.window == other.window):
 raise TypeError("Windows differ")
 elif self.symbol != other.symbol:
 raise ValueError("Polynomial symbols differ")
 return other.coef
 return other
 
 def __init__(self, coef, domain=None, window=None, symbol='x'):
 [coef] = pu.as_series([coef], trim=False)
 self.coef = coef
 
 if domain is not None:
 [domain] = pu.as_series([domain], trim=False)
 if len(domain) != 2:
 raise ValueError("Domain has wrong number of elements.")
 self.domain = domain
 
 if window is not None:
 [window] = pu.as_series([window], trim=False)
 if len(window) != 2:
 raise ValueError("Window has wrong number of elements.")
 self.window = window
 
 # Validation for symbol
 try:
 if not symbol.isidentifier():
 raise ValueError(
 "Symbol string must be a valid Python identifier"
 )
 # If a user passes in something other than a string, the above
 # results in an AttributeError. Catch this and raise a more
 # informative exception
 except AttributeError:
 raise TypeError("Symbol must be a non-empty string")
 
 self._symbol = symbol
 
 def __repr__(self):
 coef = repr(self.coef)[6:-1]
 domain = repr(self.domain)[6:-1]
 window = repr(self.window)[6:-1]
 name = self.__class__.__name__
 return (f"{name}({coef}, domain={domain}, window={window}, "
 f"symbol='{self.symbol}')")
 
 def __format__(self, fmt_str):
 if fmt_str == '':
 return self.__str__()
 if fmt_str not in ('ascii', 'unicode'):
 raise ValueError(
 f"Unsupported format string '{fmt_str}' passed to "
 f"{self.__class__}.__format__. Valid options are "
 f"'ascii' and 'unicode'"
 )
 if fmt_str == 'ascii':
 return self._generate_string(self._str_term_ascii)
 return self._generate_string(self._str_term_unicode)
 
 def __str__(self):
 if self._use_unicode:
 return self._generate_string(self._str_term_unicode)
 return self._generate_string(self._str_term_ascii)
 
 def _generate_string(self, term_method):
 """
 Generate the full string representation of the polynomial, using
 ``term_method`` to generate each polynomial term.
 """
 # Get configuration for line breaks
 linewidth = np.get_printoptions().get('linewidth', 75)
 if linewidth < 1:
 linewidth = 1
 out = pu.format_float(self.coef[0])
 for i, coef in enumerate(self.coef[1:]):
 out += " "
 power = str(i + 1)
 # Polynomial coefficient
 # The coefficient array can be an object array with elements that
 # will raise a TypeError with >= 0 (e.g. strings or Python
 # complex). In this case, represent the coefficient as-is.
 try:
 if coef >= 0:
 next_term = f"+ " + pu.format_float(coef, parens=True)
 else:
 next_term = f"- " + pu.format_float(-coef, parens=True)
 except TypeError:
 next_term = f"+ {coef}"
 # Polynomial term
 next_term += term_method(power, self.symbol)
 # Length of the current line with next term added
 line_len = len(out.split('\n')[-1]) + len(next_term)
 # If not the last term in the polynomial, it will be two
 # characters longer due to the +/- with the next term
 if i < len(self.coef[1:]) - 1:
 line_len += 2
 # Handle linebreaking
 if line_len >= linewidth:
 next_term = next_term.replace(" ", "\n", 1)
 out += next_term
 return out
 
 @classmethod
 def _str_term_unicode(cls, i, arg_str):
 """
 String representation of single polynomial term using unicode
 characters for superscripts and subscripts.
 """
 if cls.basis_name is None:
 raise NotImplementedError(
 "Subclasses must define either a basis_name, or override "
 "_str_term_unicode(cls, i, arg_str)"
 )
 return (f"·{cls.basis_name}{i.translate(cls._subscript_mapping)}"
 f"({arg_str})")
 
 @classmethod
 def _str_term_ascii(cls, i, arg_str):
 """
 String representation of a single polynomial term using ** and _ to
 represent superscripts and subscripts, respectively.
 """
 if cls.basis_name is None:
 raise NotImplementedError(
 "Subclasses must define either a basis_name, or override "
 "_str_term_ascii(cls, i, arg_str)"
 )
 return f" {cls.basis_name}_{i}({arg_str})"
 
 @classmethod
 def _repr_latex_term(cls, i, arg_str, needs_parens):
 if cls.basis_name is None:
 raise NotImplementedError(
 "Subclasses must define either a basis name, or override "
 "_repr_latex_term(i, arg_str, needs_parens)")
 # since we always add parens, we don't care if the expression needs them
 return f"{{{cls.basis_name}}}_{{{i}}}({arg_str})"
 
 @staticmethod
 def _repr_latex_scalar(x, parens=False):
 # TODO: we're stuck with disabling math formatting until we handle
 # exponents in this function
 return r'\text{{{}}}'.format(pu.format_float(x, parens=parens))
 
 def _repr_latex_(self):
 # get the scaled argument string to the basis functions
 off, scale = self.mapparms()
 if off == 0 and scale == 1:
 term = self.symbol
 needs_parens = False
 elif scale == 1:
 term = f"{self._repr_latex_scalar(off)} + {self.symbol}"
 needs_parens = True
 elif off == 0:
 term = f"{self._repr_latex_scalar(scale)}{self.symbol}"
 needs_parens = True
 else:
 term = (
 f"{self._repr_latex_scalar(off)} + "
 f"{self._repr_latex_scalar(scale)}{self.symbol}"
 )
 needs_parens = True
 
 mute = r"\color{{LightGray}}{{{}}}".format
 
 parts = []
 for i, c in enumerate(self.coef):
 # prevent duplication of + and - signs
 if i == 0:
 coef_str = f"{self._repr_latex_scalar(c)}"
 elif not isinstance(c, numbers.Real):
 coef_str = f" + ({self._repr_latex_scalar(c)})"
 elif not np.signbit(c):
 coef_str = f" + {self._repr_latex_scalar(c, parens=True)}"
 else:
 coef_str = f" - {self._repr_latex_scalar(-c, parens=True)}"
 
 # produce the string for the term
 term_str = self._repr_latex_term(i, term, needs_parens)
 if term_str == '1':
 part = coef_str
 else:
 part = rf"{coef_str}\,{term_str}"
 
 if c == 0:
 part = mute(part)
 
 parts.append(part)
 
 if parts:
 body = ''.join(parts)
 else:
 # in case somehow there are no coefficients at all
 body = '0'
 
 return rf"${self.symbol} \mapsto {body}$"
 
 
 
 # Pickle and copy
 
 def __getstate__(self):
 ret = self.__dict__.copy()
 ret['coef'] = self.coef.copy()
 ret['domain'] = self.domain.copy()
 ret['window'] = self.window.copy()
 ret['symbol'] = self.symbol
 return ret
 
 def __setstate__(self, dict):
 self.__dict__ = dict
 
 # Call
 
 def __call__(self, arg):
 off, scl = pu.mapparms(self.domain, self.window)
 arg = off + scl*arg
 return self._val(arg, self.coef)
 
 def __iter__(self):
 return iter(self.coef)
 
 def __len__(self):
 return len(self.coef)
 
 # Numeric properties.
 
 def __neg__(self):
 return self.__class__(
 -self.coef, self.domain, self.window, self.symbol
 )
 
 def __pos__(self):
 return self
 
 def __add__(self, other):
 othercoef = self._get_coefficients(other)
 try:
 coef = self._add(self.coef, othercoef)
 except Exception:
 return NotImplemented
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def __sub__(self, other):
 othercoef = self._get_coefficients(other)
 try:
 coef = self._sub(self.coef, othercoef)
 except Exception:
 return NotImplemented
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def __mul__(self, other):
 othercoef = self._get_coefficients(other)
 try:
 coef = self._mul(self.coef, othercoef)
 except Exception:
 return NotImplemented
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def __truediv__(self, other):
 # there is no true divide if the rhs is not a Number, although it
 # could return the first n elements of an infinite series.
 # It is hard to see where n would come from, though.
 if not isinstance(other, numbers.Number) or isinstance(other, bool):
 raise TypeError(
 f"unsupported types for true division: "
 f"'{type(self)}', '{type(other)}'"
 )
 return self.__floordiv__(other)
 
 def __floordiv__(self, other):
 res = self.__divmod__(other)
 if res is NotImplemented:
 return res
 return res[0]
 
 def __mod__(self, other):
 res = self.__divmod__(other)
 if res is NotImplemented:
 return res
 return res[1]
 
 def __divmod__(self, other):
 othercoef = self._get_coefficients(other)
 try:
 quo, rem = self._div(self.coef, othercoef)
 except ZeroDivisionError:
 raise
 except Exception:
 return NotImplemented
 quo = self.__class__(quo, self.domain, self.window, self.symbol)
 rem = self.__class__(rem, self.domain, self.window, self.symbol)
 return quo, rem
 
 def __pow__(self, other):
 coef = self._pow(self.coef, other, maxpower=self.maxpower)
 res = self.__class__(coef, self.domain, self.window, self.symbol)
 return res
 
 def __radd__(self, other):
 try:
 coef = self._add(other, self.coef)
 except Exception:
 return NotImplemented
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def __rsub__(self, other):
 try:
 coef = self._sub(other, self.coef)
 except Exception:
 return NotImplemented
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def __rmul__(self, other):
 try:
 coef = self._mul(other, self.coef)
 except Exception:
 return NotImplemented
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def __rdiv__(self, other):
 # set to __floordiv__ /.
 return self.__rfloordiv__(other)
 
 def __rtruediv__(self, other):
 # An instance of ABCPolyBase is not considered a
 # Number.
 return NotImplemented
 
 def __rfloordiv__(self, other):
 res = self.__rdivmod__(other)
 if res is NotImplemented:
 return res
 return res[0]
 
 def __rmod__(self, other):
 res = self.__rdivmod__(other)
 if res is NotImplemented:
 return res
 return res[1]
 
 def __rdivmod__(self, other):
 try:
 quo, rem = self._div(other, self.coef)
 except ZeroDivisionError:
 raise
 except Exception:
 return NotImplemented
 quo = self.__class__(quo, self.domain, self.window, self.symbol)
 rem = self.__class__(rem, self.domain, self.window, self.symbol)
 return quo, rem
 
 def __eq__(self, other):
 res = (isinstance(other, self.__class__) and
 np.all(self.domain == other.domain) and
 np.all(self.window == other.window) and
 (self.coef.shape == other.coef.shape) and
 np.all(self.coef == other.coef) and
 (self.symbol == other.symbol))
 return res
 
 def __ne__(self, other):
 return not self.__eq__(other)
 
 #
 # Extra methods.
 #
 
 def copy(self):
 """Return a copy.
 
 Returns
 -------
 new_series : series
 Copy of self.
 
 """
 return self.__class__(self.coef, self.domain, self.window, self.symbol)
 
 def degree(self):
 """The degree of the series.
 
 .. versionadded:: 1.5.0
 
 Returns
 -------
 degree : int
 Degree of the series, one less than the number of coefficients.
 
 Examples
 --------
 
 Create a polynomial object for ``1 + 7*x + 4*x**2``:
 
 >>> poly = np.polynomial.Polynomial([1, 7, 4])
 >>> print(poly)
 1.0 + 7.0·x + 4.0·x²
 >>> poly.degree()
 2
 
 Note that this method does not check for non-zero coefficients.
 You must trim the polynomial to remove any trailing zeroes:
 
 >>> poly = np.polynomial.Polynomial([1, 7, 0])
 >>> print(poly)
 1.0 + 7.0·x + 0.0·x²
 >>> poly.degree()
 2
 >>> poly.trim().degree()
 1
 
 """
 return len(self) - 1
 
 def cutdeg(self, deg):
 """Truncate series to the given degree.
 
 Reduce the degree of the series to `deg` by discarding the
 high order terms. If `deg` is greater than the current degree a
 copy of the current series is returned. This can be useful in least
 squares where the coefficients of the high degree terms may be very
 small.
 
 .. versionadded:: 1.5.0
 
 Parameters
 ----------
 deg : non-negative int
 The series is reduced to degree `deg` by discarding the high
 order terms. The value of `deg` must be a non-negative integer.
 
 Returns
 -------
 new_series : series
 New instance of series with reduced degree.
 
 """
 return self.truncate(deg + 1)
 
 def trim(self, tol=0):
 """Remove trailing coefficients
 
 Remove trailing coefficients until a coefficient is reached whose
 absolute value greater than `tol` or the beginning of the series is
 reached. If all the coefficients would be removed the series is set
 to ``[0]``. A new series instance is returned with the new
 coefficients.  The current instance remains unchanged.
 
 Parameters
 ----------
 tol : non-negative number.
 All trailing coefficients less than `tol` will be removed.
 
 Returns
 -------
 new_series : series
 New instance of series with trimmed coefficients.
 
 """
 coef = pu.trimcoef(self.coef, tol)
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def truncate(self, size):
 """Truncate series to length `size`.
 
 Reduce the series to length `size` by discarding the high
 degree terms. The value of `size` must be a positive integer. This
 can be useful in least squares where the coefficients of the
 high degree terms may be very small.
 
 Parameters
 ----------
 size : positive int
 The series is reduced to length `size` by discarding the high
 degree terms. The value of `size` must be a positive integer.
 
 Returns
 -------
 new_series : series
 New instance of series with truncated coefficients.
 
 """
 isize = int(size)
 if isize != size or isize < 1:
 raise ValueError("size must be a positive integer")
 if isize >= len(self.coef):
 coef = self.coef
 else:
 coef = self.coef[:isize]
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def convert(self, domain=None, kind=None, window=None):
 """Convert series to a different kind and/or domain and/or window.
 
 Parameters
 ----------
 domain : array_like, optional
 The domain of the converted series. If the value is None,
 the default domain of `kind` is used.
 kind : class, optional
 The polynomial series type class to which the current instance
 should be converted. If kind is None, then the class of the
 current instance is used.
 window : array_like, optional
 The window of the converted series. If the value is None,
 the default window of `kind` is used.
 
 Returns
 -------
 new_series : series
 The returned class can be of different type than the current
 instance and/or have a different domain and/or different
 window.
 
 Notes
 -----
 Conversion between domains and class types can result in
 numerically ill defined series.
 
 """
 if kind is None:
 kind = self.__class__
 if domain is None:
 domain = kind.domain
 if window is None:
 window = kind.window
 return self(kind.identity(domain, window=window, symbol=self.symbol))
 
 def mapparms(self):
 """Return the mapping parameters.
 
 The returned values define a linear map ``off + scl*x`` that is
 applied to the input arguments before the series is evaluated. The
 map depends on the ``domain`` and ``window``; if the current
 ``domain`` is equal to the ``window`` the resulting map is the
 identity.  If the coefficients of the series instance are to be
 used by themselves outside this class, then the linear function
 must be substituted for the ``x`` in the standard representation of
 the base polynomials.
 
 Returns
 -------
 off, scl : float or complex
 The mapping function is defined by ``off + scl*x``.
 
 Notes
 -----
 If the current domain is the interval ``[l1, r1]`` and the window
 is ``[l2, r2]``, then the linear mapping function ``L`` is
 defined by the equations::
 
 L(l1) = l2
 L(r1) = r2
 
 """
 return pu.mapparms(self.domain, self.window)
 
 def integ(self, m=1, k=[], lbnd=None):
 """Integrate.
 
 Return a series instance that is the definite integral of the
 current series.
 
 Parameters
 ----------
 m : non-negative int
 The number of integrations to perform.
 k : array_like
 Integration constants. The first constant is applied to the
 first integration, the second to the second, and so on. The
 list of values must less than or equal to `m` in length and any
 missing values are set to zero.
 lbnd : Scalar
 The lower bound of the definite integral.
 
 Returns
 -------
 new_series : series
 A new series representing the integral. The domain is the same
 as the domain of the integrated series.
 
 """
 off, scl = self.mapparms()
 if lbnd is None:
 lbnd = 0
 else:
 lbnd = off + scl*lbnd
 coef = self._int(self.coef, m, k, lbnd, 1./scl)
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def deriv(self, m=1):
 """Differentiate.
 
 Return a series instance of that is the derivative of the current
 series.
 
 Parameters
 ----------
 m : non-negative int
 Find the derivative of order `m`.
 
 Returns
 -------
 new_series : series
 A new series representing the derivative. The domain is the same
 as the domain of the differentiated series.
 
 """
 off, scl = self.mapparms()
 coef = self._der(self.coef, m, scl)
 return self.__class__(coef, self.domain, self.window, self.symbol)
 
 def roots(self):
 """Return the roots of the series polynomial.
 
 Compute the roots for the series. Note that the accuracy of the
 roots decreases the further outside the `domain` they lie.
 
 Returns
 -------
 roots : ndarray
 Array containing the roots of the series.
 
 """
 roots = self._roots(self.coef)
 return pu.mapdomain(roots, self.window, self.domain)
 
 def linspace(self, n=100, domain=None):
 """Return x, y values at equally spaced points in domain.
 
 Returns the x, y values at `n` linearly spaced points across the
 domain.  Here y is the value of the polynomial at the points x. By
 default the domain is the same as that of the series instance.
 This method is intended mostly as a plotting aid.
 
 .. versionadded:: 1.5.0
 
 Parameters
 ----------
 n : int, optional
 Number of point pairs to return. The default value is 100.
 domain : {None, array_like}, optional
 If not None, the specified domain is used instead of that of
 the calling instance. It should be of the form ``[beg,end]``.
 The default is None which case the class domain is used.
 
 Returns
 -------
 x, y : ndarray
 x is equal to linspace(self.domain[0], self.domain[1], n) and
 y is the series evaluated at element of x.
 
 """
 if domain is None:
 domain = self.domain
 x = np.linspace(domain[0], domain[1], n)
 y = self(x)
 return x, y
 
 @classmethod
 def fit(cls, x, y, deg, domain=None, rcond=None, full=False, w=None,
 window=None, symbol='x'):
 """Least squares fit to data.
 
 Return a series instance that is the least squares fit to the data
 `y` sampled at `x`. The domain of the returned instance can be
 specified and this will often result in a superior fit with less
 chance of ill conditioning.
 
 Parameters
 ----------
 x : array_like, shape (M,)
 x-coordinates of the M sample points ``(x[i], y[i])``.
 y : array_like, shape (M,)
 y-coordinates of the M sample points ``(x[i], y[i])``.
 deg : int or 1-D array_like
 Degree(s) of the fitting polynomials. If `deg` is a single integer
 all terms up to and including the `deg`'th term are included in the
 fit. For NumPy versions >= 1.11.0 a list of integers specifying the
 degrees of the terms to include may be used instead.
 domain : {None, [beg, end], []}, optional
 Domain to use for the returned series. If ``None``,
 then a minimal domain that covers the points `x` is chosen.  If
 ``[]`` the class domain is used. The default value was the
 class domain in NumPy 1.4 and ``None`` in later versions.
 The ``[]`` option was added in numpy 1.5.0.
 rcond : float, optional
 Relative condition number of the fit. Singular values smaller
 than this relative to the largest singular value will be
 ignored. The default value is len(x)*eps, where eps is the
 relative precision of the float type, about 2e-16 in most
 cases.
 full : bool, optional
 Switch determining nature of return value. When it is False
 (the default) just the coefficients are returned, when True
 diagnostic information from the singular value decomposition is
 also returned.
 w : array_like, shape (M,), optional
 Weights. If not None, the weight ``w[i]`` applies to the unsquared
 residual ``y[i] - y_hat[i]`` at ``x[i]``. Ideally the weights are
 chosen so that the errors of the products ``w[i]*y[i]`` all have
 the same variance.  When using inverse-variance weighting, use
 ``w[i] = 1/sigma(y[i])``.  The default value is None.
 
 .. versionadded:: 1.5.0
 window : {[beg, end]}, optional
 Window to use for the returned series. The default
 value is the default class domain
 
 .. versionadded:: 1.6.0
 symbol : str, optional
 Symbol representing the independent variable. Default is 'x'.
 
 Returns
 -------
 new_series : series
 A series that represents the least squares fit to the data and
 has the domain and window specified in the call. If the
 coefficients for the unscaled and unshifted basis polynomials are
 of interest, do ``new_series.convert().coef``.
 
 [resid, rank, sv, rcond] : list
 These values are only returned if ``full == True``
 
 - resid -- sum of squared residuals of the least squares fit
 - rank -- the numerical rank of the scaled Vandermonde matrix
 - sv -- singular values of the scaled Vandermonde matrix
 - rcond -- value of `rcond`.
 
 For more details, see `linalg.lstsq`.
 
 """
 if domain is None:
 domain = pu.getdomain(x)
 elif type(domain) is list and len(domain) == 0:
 domain = cls.domain
 
 if window is None:
 window = cls.window
 
 xnew = pu.mapdomain(x, domain, window)
 res = cls._fit(xnew, y, deg, w=w, rcond=rcond, full=full)
 if full:
 [coef, status] = res
 return (
 cls(coef, domain=domain, window=window, symbol=symbol), status
 )
 else:
 coef = res
 return cls(coef, domain=domain, window=window, symbol=symbol)
 
 @classmethod
 def fromroots(cls, roots, domain=[], window=None, symbol='x'):
 """Return series instance that has the specified roots.
 
 Returns a series representing the product
 ``(x - r[0])*(x - r[1])*...*(x - r[n-1])``, where ``r`` is a
 list of roots.
 
 Parameters
 ----------
 roots : array_like
 List of roots.
 domain : {[], None, array_like}, optional
 Domain for the resulting series. If None the domain is the
 interval from the smallest root to the largest. If [] the
 domain is the class domain. The default is [].
 window : {None, array_like}, optional
 Window for the returned series. If None the class window is
 used. The default is None.
 symbol : str, optional
 Symbol representing the independent variable. Default is 'x'.
 
 Returns
 -------
 new_series : series
 Series with the specified roots.
 
 """
 [roots] = pu.as_series([roots], trim=False)
 if domain is None:
 domain = pu.getdomain(roots)
 elif type(domain) is list and len(domain) == 0:
 domain = cls.domain
 
 if window is None:
 window = cls.window
 
 deg = len(roots)
 off, scl = pu.mapparms(domain, window)
 rnew = off + scl*roots
 coef = cls._fromroots(rnew) / scl**deg
 return cls(coef, domain=domain, window=window, symbol=symbol)
 
 @classmethod
 def identity(cls, domain=None, window=None, symbol='x'):
 """Identity function.
 
 If ``p`` is the returned series, then ``p(x) == x`` for all
 values of x.
 
 Parameters
 ----------
 domain : {None, array_like}, optional
 If given, the array must be of the form ``[beg, end]``, where
 ``beg`` and ``end`` are the endpoints of the domain. If None is
 given then the class domain is used. The default is None.
 window : {None, array_like}, optional
 If given, the resulting array must be if the form
 ``[beg, end]``, where ``beg`` and ``end`` are the endpoints of
 the window. If None is given then the class window is used. The
 default is None.
 symbol : str, optional
 Symbol representing the independent variable. Default is 'x'.
 
 Returns
 -------
 new_series : series
 Series of representing the identity.
 
 """
 if domain is None:
 domain = cls.domain
 if window is None:
 window = cls.window
 off, scl = pu.mapparms(window, domain)
 coef = cls._line(off, scl)
 return cls(coef, domain, window, symbol)
 
 @classmethod
 def basis(cls, deg, domain=None, window=None, symbol='x'):
 """Series basis polynomial of degree `deg`.
 
 Returns the series representing the basis polynomial of degree `deg`.
 
 .. versionadded:: 1.7.0
 
 Parameters
 ----------
 deg : int
 Degree of the basis polynomial for the series. Must be >= 0.
 domain : {None, array_like}, optional
 If given, the array must be of the form ``[beg, end]``, where
 ``beg`` and ``end`` are the endpoints of the domain. If None is
 given then the class domain is used. The default is None.
 window : {None, array_like}, optional
 If given, the resulting array must be if the form
 ``[beg, end]``, where ``beg`` and ``end`` are the endpoints of
 the window. If None is given then the class window is used. The
 default is None.
 symbol : str, optional
 Symbol representing the independent variable. Default is 'x'.
 
 Returns
 -------
 new_series : series
 A series with the coefficient of the `deg` term set to one and
 all others zero.
 
 """
 if domain is None:
 domain = cls.domain
 if window is None:
 window = cls.window
 ideg = int(deg)
 
 if ideg != deg or ideg < 0:
 raise ValueError("deg must be non-negative integer")
 return cls([0]*ideg + [1], domain, window, symbol)
 
 @classmethod
 def cast(cls, series, domain=None, window=None):
 """Convert series to series of this class.
 
 The `series` is expected to be an instance of some polynomial
 series of one of the types supported by by the numpy.polynomial
 module, but could be some other class that supports the convert
 method.
 
 .. versionadded:: 1.7.0
 
 Parameters
 ----------
 series : series
 The series instance to be converted.
 domain : {None, array_like}, optional
 If given, the array must be of the form ``[beg, end]``, where
 ``beg`` and ``end`` are the endpoints of the domain. If None is
 given then the class domain is used. The default is None.
 window : {None, array_like}, optional
 If given, the resulting array must be if the form
 ``[beg, end]``, where ``beg`` and ``end`` are the endpoints of
 the window. If None is given then the class window is used. The
 default is None.
 
 Returns
 -------
 new_series : series
 A series of the same kind as the calling class and equal to
 `series` when evaluated.
 
 See Also
 --------
 convert : similar instance method
 
 """
 if domain is None:
 domain = cls.domain
 if window is None:
 window = cls.window
 return series.convert(domain, cls, window)
 
 |