123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- # real_multivar_taylor.py: class implementing Taylor expansions of multiple real variables
- # Copyright 2022 Romain Serra
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- from typing import List, Iterable, Union
- import math
- import numpy as np
- from swiftt.taylor.complex_multivar_taylor import TaylorExpansAbstract, ComplexMultivarTaylor
- from swiftt.taylor.taylor_map import RealTaylorMap
- from swiftt.interval import Interval
- class RealMultivarTaylor(ComplexMultivarTaylor):
- """Class for Taylor expansions of real variables.
- """
- # intrinsic functions for reals
- _sqrt_cst = math.sqrt
- _log_cst, _exp_cst = math.log, math.exp
- _cos_cst, _sin_cst = math.cos, math.sin
- _cosh_cst, _sinh_cst = math.cosh, math.sinh
- _tan_cst, _tanh_cst = math.tan, math.tanh
- def __init__(self, n_var: int, order: int, var_names: List[str]) -> None:
- """Constructor for Taylor expansions of multiple real variables. Result has no assigned coefficients.
- Args:
- n_var (int): number of variables in algebra.
- order (int): order of algebra.
- var_names (List[str]): name of variables in algebra.
- """
- TaylorExpansAbstract.__init__(self, n_var, order, np.float64, var_names)
- def __float__(self) -> float:
- """Method to cast Taylor expansion as a scalar (according to its constant part).
- Returns:
- float: constant part of expansion seen as a scalar.
- """
- return float(self.const)
- def __int__(self) -> int:
- """Method to cast Taylor expansion as a integer (according to its constant part).
- Returns:
- int: constant part of expansion casted as an integer.
- """
- return int(self.const)
- @classmethod
- def from_complex_expansion(cls, expansion: ComplexMultivarTaylor) -> "RealMultivarTaylor":
- """Method to recast a complex Taylor expansion into a real one.
- Args:
- expansion (ComplexMultivarTaylor): original Taylor expansion.
- Returns:
- RealMultivarTaylor: Taylor expansion where all coefficients are real parts of the input's.
- """
- real_expansion = cls(expansion.n_var, expansion.order, expansion.var_names)
- real_expansion.coeff = np.real(expansion.coeff)
- return real_expansion
- def __str__(self) -> str:
- """Method to cast Taylor expansion as a string. Overwrites parent implementation assuming complex coefficients.
- Returns:
- str: string representing the Taylor expansion.
- """
- string = str(self.const)
- var_names = self.var_names
- if not self.is_trivial():
- for exponent, index_coeff in self.get_mapping_monom().items():
- if self._coeff[index_coeff] != 0. and index_coeff != 0:
- sign = " + " if self._coeff[index_coeff] > 0. else " - "
- string += sign + str(abs(self._coeff[index_coeff]))
- for index_var, power_var in enumerate(exponent):
- if power_var != 0:
- string += " * " + var_names[index_var]
- if power_var > 1:
- string += "**" + str(power_var)
- return string + " + " + self.remainder_term
- def bounder(self, domains: Iterable[Interval]) -> Interval:
- """Method to evaluate the polynomial part of the Taylor expansion on a Cartesian product of segments via
- interval arithmetic.
- Args:
- domains (Iterable[Interval]): input intervals for expansion's variables.
- Returns:
- Interval: image of inputted intervals through polynomial part.
- """
- if self.is_trivial():
- return Interval.singleton(self.const)
- # order of at least one
- output = Interval.singleton(self.const)
- for i, domain in enumerate(domains, 1):
- output += self._coeff[i] * domain
- # non-linear terms
- if self._order > 1:
- powers = []
- for domain in domains:
- powers_xi = [1., domain]
- for j in range(2, self._order + 1):
- powers_xi.append(domain ** j)
- powers.append(powers_xi)
- for exponent, index_coeff in self.get_mapping_monom().items():
- if self._coeff[index_coeff] != 0. and index_coeff > self._n_var:
- product = powers[0][exponent[0]]
- for index_var, power_var in enumerate(exponent[1:], 1):
- product *= powers[index_var][power_var]
- output += self._coeff[index_coeff] * product
- return output
- def __call__(self, *args, **kwargs):
- """Method for calling the Taylor expansion. Wraps several possibilities: evaluation on a point or a Cartesian
- product of intervals as well as composition with a map.
- Returns:
- Union[RealTaylorMap, Interval, numpy.ndarray]: Taylor expansion called on input.
- """
- if isinstance(args[0], RealTaylorMap):
- # composition
- return RealMultivarTaylor.compose(self, *args, **kwargs)
- if isinstance(args[0][0], Interval):
- # bounder
- return self.bounder(args[0])
- # evaluation
- return self.pointwise_eval(args[0])
- def __lt__(self, other: Union["RealMultivarTaylor", float]) -> bool:
- """Method enabling "<" inequality comparison with other Taylor expansions and scalars.
- Args:
- other (Union[RealMultivarTaylor, float])): quantity to be compared with.
- Returns:
- bool: if the input is a Taylor expansion in same algebra, compares coefficients pairwise
- (according to sorted monomials) until one is strictly smaller than the other. If the input is a
- scalar, performs comparison on Taylor expansion with only a constant part equal to it.
- """
- if isinstance(other, RealMultivarTaylor):
- if self.is_in_same_algebra(other):
- for coeff1, coeff2 in zip(self._coeff, other._coeff):
- if coeff1 != coeff2:
- return coeff1 < coeff2
- return False
- # scalar case
- return self < self.create_const_expansion(other)
- def __le__(self, other: Union["RealMultivarTaylor", float]) -> bool:
- """Method enabling "<=" inequality comparison with other Taylor expansions and scalars.
- Args:
- other (Union[RealMultivarTaylor, float])): quantity to be compared with.
- Returns:
- bool: returns logical union of "=" and "<".
- """
- return not self.__gt__(other)
- def __gt__(self, other: Union["RealMultivarTaylor", float]) -> bool:
- """Method enabling ">" inequality comparison with other Taylor expansions and scalars.
- Args:
- other (Union[RealMultivarTaylor, float])): quantity to be compared with.
- Returns:
- bool: if the input is a Taylor expansion in same algebra, compares coefficients pairwise
- (according to sorted monomials) until one is strictly greater than the other. If the input is a
- scalar, performs comparison on Taylor expansion with only a constant part equal to it.
- """
- if isinstance(other, RealMultivarTaylor):
- if self.is_in_same_algebra(other):
- for coeff1, coeff2 in zip(self._coeff, other._coeff):
- if coeff1 != coeff2:
- return coeff1 > coeff2
- return False
- # scalar case
- return self > self.create_const_expansion(other)
- def __ge__(self, other: Union["RealMultivarTaylor", float]) -> bool:
- """Method enabling ">=" inequality comparison with other Taylor expansions and scalars.
- Args:
- other (Union[RealMultivarTaylor, float])): quantity to be compared with.
- Returns:
- bool: returns logical union of "=" and ">".
- """
- return not self.__lt__(other)
- def __abs__(self) -> "RealMultivarTaylor":
- """Method implementation the absolute value for real Taylor expansions.
- Returns:
- RealMultivarTaylor: Taylor expansion composed with absolute value from the left hand side.
- """
- const = self.const
- if const == 0.:
- raise ValueError("The absolute value is not differentiable at zero.")
- return self.copy() if const > 0. else -self
- def __floor__(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the floor function.
- Returns:
- RealMultivarTaylor: floor of Taylor expansion.
- """
- return self.create_const_expansion(math.floor(self.const))
- def __ceil__(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the ceil function.
- Returns:
- RealMultivarTaylor: ceil of Taylor expansion.
- """
- return self.create_const_expansion(math.ceil(self.const))
- def sign(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the sign function.
- Returns:
- RealMultivarTaylor: sign of Taylor expansion.
- """
- return self.create_const_expansion(np.sign(self.const))
- def cbrt(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the cubic root function.
- Returns:
- RealMultivarTaylor: cubic root of Taylor expansion.
- """
- return self ** (1. / 3.)
- @staticmethod
- def seq_deriv_atan_atanh(c: float, eps: float, order: int) -> np.ndarray:
- csq = c * c
- cd = 2. * c
- aux = -1. / (csq + eps)
- seq = np.zeros(order)
- seq[0] = 1. / (1. + eps * csq)
- if order > 1:
- seq[1] = seq[0] * cd * aux
- for i in range(2, order):
- seq[i] = (seq[i - 2] + cd * seq[i - 1]) * aux
- return seq
- def arctan(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the inverse tangent function. Here so that Numpy can use it on arrays.
- Returns:
- RealMultivarTaylor: inverse tangent of Taylor expansion.
- """
- if self.is_trivial():
- return self.create_const_expansion(math.atan(self.const))
- order = self.order
- const = self.const
- sequence = self.seq_deriv_atan_atanh(const, 1., order)
- seq = sequence / np.arange(1, order + 1)
- nilpo = self.get_nilpo_part()
- arctan = seq[-1] * nilpo
- for el in seq[-2::-1]:
- arctan.const = el
- arctan *= nilpo
- arctan.const = math.atan(const)
- return arctan
- def arctanh(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the inverse hyperbolic tangent function.
- Here so that Numpy can use it on arrays.
- Returns:
- RealMultivarTaylor: inverse hyperbolic tangent of Taylor expansion.
- """
- if self.is_trivial():
- return self.create_const_expansion(math.atanh(self.const))
- order = self.order
- const = self.const
- sequence = self.seq_deriv_atan_atanh(const, -1., order)
- seq = sequence / np.arange(1, order + 1)
- nilpo = self.get_nilpo_part()
- arctanh = seq[-1] * nilpo
- for el in seq[-2::-1]:
- arctanh.const = el
- arctanh *= nilpo
- arctanh.const = math.atanh(const)
- return arctanh
- @staticmethod
- def seq_deriv_asin_asinh(c: float, eps: float, order: int) -> np.ndarray:
- csq = c * c
- aux = -1. / (csq + eps)
- seq = np.zeros(order)
- seq[0] = 1. / math.sqrt(1. + eps * csq)
- if order > 1:
- seq[1] = seq[0] * c * aux
- inter = aux / np.arange(2, order)
- for i, el in enumerate(inter, 2):
- seq[i] = ((i - 1.) * seq[i - 2] + c * (2. * i - 1.) * seq[i - 1]) * el
- return seq
- def arcsin(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the inverse sine function.
- Returns:
- RealMultivarTaylor: inverse sine of Taylor expansion.
- """
- if self.is_trivial():
- return self.create_const_expansion(math.asin(self.const))
- order = self.order
- const = self.const
- sequence = self.seq_deriv_asin_asinh(const, -1., order)
- nilpo = self.get_nilpo_part()
- seq = sequence / np.arange(1, order + 1)
- arcsine = seq[-1] * nilpo
- for el in seq[-2::-1]:
- arcsine.const = el
- arcsine *= nilpo
- arcsine.const = math.asin(const)
- return arcsine
- def arccos(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the inverse cosine function.
- Returns:
- RealMultivarTaylor: inverse cosine of Taylor expansion.
- """
- return -self.arcsin() + math.pi / 2.
- def arcsinh(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the inverse hyperbolic sine function.
- Returns:
- RealMultivarTaylor: inverse hyperbolic sine of Taylor expansion.
- """
- if self.is_trivial():
- return self.create_const_expansion(math.asinh(self.const))
- order = self.order
- const = self.const
- sequence = self.seq_deriv_asin_asinh(const, 1., order)
- seq = sequence / np.arange(1, order + 1)
- nilpo = self.get_nilpo_part()
- arcsineh = seq[-1] * nilpo
- for el in seq[-2::-1]:
- arcsineh.const = el
- arcsineh *= nilpo
- arcsineh.const = math.asinh(const)
- return arcsineh
- def arccosh(self) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the inverse hyperbolic cosine function.
- Returns:
- RealMultivarTaylor: inverse hyperbolic cosine of Taylor expansion.
- """
- if self.is_trivial():
- return self.create_const_expansion(math.acosh(self.const))
- order = self.order
- const = self.const
- c2 = const * const
- aux = 1. / (1. - c2)
- seq = np.zeros(order)
- seq[0] = 1. / math.sqrt(c2 - 1.)
- if order > 1:
- seq[1] = const * seq[0] * aux
- for i in range(2, order):
- seq[i] = ((i - 1.) * seq[i - 2] + (2. * i - 1.) * const * seq[i - 1]) * aux / i
- seq /= np.arange(1, order + 1)
- nilpo = self.get_nilpo_part()
- arccosineh = seq[-1] * nilpo
- for el in seq[-2::-1]:
- arccosineh.const = el
- arccosineh *= nilpo
- arccosineh.const = math.acosh(const)
- return arccosineh
- def arctan2(self, q: Union["RealMultivarTaylor", float]) -> "RealMultivarTaylor":
- """Version for Taylor expansions of the arctan2.
- Args:
- Union["RealMultivarTaylor", float]: second argument of arctan2
- Returns:
- RealMultivarTaylor: arctan2(self, q).
- """
- if q != 0.:
- output = (self / q).arctan()
- elif self.const != 0.:
- output = -(q / self).arctan()
- else:
- raise ValueError
- output.const = math.atan2(self.const, float(q))
- return output
|