"""
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 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.
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.
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
@property
@abc.abstractmethod
def domain(self):
pass
@property
@abc.abstractmethod
def window(self):
pass
@property
@abc.abstractmethod
def nickname(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")
return other.coef
return other
def __init__(self, coef, domain=None, window=None):
[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
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})"
def __str__(self):
coef = str(self.coef)
name = self.nickname
return f"{name}({coef})"
@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):
# TODO: we're stuck with disabling math formatting until we handle
# exponents in this function
return r'\text{{{}}}'.format(x)
def _repr_latex_(self):
# get the scaled argument string to the basis functions
off, scale = self.mapparms()
if off == 0 and scale == 1:
term = 'x'
needs_parens = False
elif scale == 1:
term = f"{self._repr_latex_scalar(off)} + x"
needs_parens = True
elif off == 0:
term = f"{self._repr_latex_scalar(scale)}x"
needs_parens = True
else:
term = (
f"{self._repr_latex_scalar(off)} + "
f"{self._repr_latex_scalar(scale)}x"
)
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)}"
else:
coef_str = f" - {self._repr_latex_scalar(-c)}"
# 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}"
Loading ...