real_multivar_taylor.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. # real_multivar_taylor.py: class implementing Taylor expansions of multiple real variables
  2. # Copyright 2022 Romain Serra
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import List, Iterable, Union
  15. import math
  16. import numpy as np
  17. from swiftt.taylor.complex_multivar_taylor import TaylorExpansAbstract, ComplexMultivarTaylor
  18. from swiftt.taylor.taylor_map import RealTaylorMap
  19. from swiftt.interval import Interval
  20. class RealMultivarTaylor(ComplexMultivarTaylor):
  21. """Class for Taylor expansions of real variables.
  22. """
  23. # intrinsic functions for reals
  24. _sqrt_cst = math.sqrt
  25. _log_cst, _exp_cst = math.log, math.exp
  26. _cos_cst, _sin_cst = math.cos, math.sin
  27. _cosh_cst, _sinh_cst = math.cosh, math.sinh
  28. _tan_cst, _tanh_cst = math.tan, math.tanh
  29. def __init__(self, n_var: int, order: int, var_names: List[str]) -> None:
  30. """Constructor for Taylor expansions of multiple real variables. Result has no assigned coefficients.
  31. Args:
  32. n_var (int): number of variables in algebra.
  33. order (int): order of algebra.
  34. var_names (List[str]): name of variables in algebra.
  35. """
  36. TaylorExpansAbstract.__init__(self, n_var, order, np.float64, var_names)
  37. def __float__(self) -> float:
  38. """Method to cast Taylor expansion as a scalar (according to its constant part).
  39. Returns:
  40. float: constant part of expansion seen as a scalar.
  41. """
  42. return float(self.const)
  43. def __int__(self) -> int:
  44. """Method to cast Taylor expansion as a integer (according to its constant part).
  45. Returns:
  46. int: constant part of expansion casted as an integer.
  47. """
  48. return int(self.const)
  49. @classmethod
  50. def from_complex_expansion(cls, expansion: ComplexMultivarTaylor) -> "RealMultivarTaylor":
  51. """Method to recast a complex Taylor expansion into a real one.
  52. Args:
  53. expansion (ComplexMultivarTaylor): original Taylor expansion.
  54. Returns:
  55. RealMultivarTaylor: Taylor expansion where all coefficients are real parts of the input's.
  56. """
  57. real_expansion = cls(expansion.n_var, expansion.order, expansion.var_names)
  58. real_expansion.coeff = np.real(expansion.coeff)
  59. return real_expansion
  60. def __str__(self) -> str:
  61. """Method to cast Taylor expansion as a string. Overwrites parent implementation assuming complex coefficients.
  62. Returns:
  63. str: string representing the Taylor expansion.
  64. """
  65. string = str(self.const)
  66. var_names = self.var_names
  67. if not self.is_trivial():
  68. for exponent, index_coeff in self.get_mapping_monom().items():
  69. if self._coeff[index_coeff] != 0. and index_coeff != 0:
  70. sign = " + " if self._coeff[index_coeff] > 0. else " - "
  71. string += sign + str(abs(self._coeff[index_coeff]))
  72. for index_var, power_var in enumerate(exponent):
  73. if power_var != 0:
  74. string += " * " + var_names[index_var]
  75. if power_var > 1:
  76. string += "**" + str(power_var)
  77. return string + " + " + self.remainder_term
  78. def bounder(self, domains: Iterable[Interval]) -> Interval:
  79. """Method to evaluate the polynomial part of the Taylor expansion on a Cartesian product of segments via
  80. interval arithmetic.
  81. Args:
  82. domains (Iterable[Interval]): input intervals for expansion's variables.
  83. Returns:
  84. Interval: image of inputted intervals through polynomial part.
  85. """
  86. if self.is_trivial():
  87. return Interval.singleton(self.const)
  88. # order of at least one
  89. output = Interval.singleton(self.const)
  90. for i, domain in enumerate(domains, 1):
  91. output += self._coeff[i] * domain
  92. # non-linear terms
  93. if self._order > 1:
  94. powers = []
  95. for domain in domains:
  96. powers_xi = [1., domain]
  97. for j in range(2, self._order + 1):
  98. powers_xi.append(domain ** j)
  99. powers.append(powers_xi)
  100. for exponent, index_coeff in self.get_mapping_monom().items():
  101. if self._coeff[index_coeff] != 0. and index_coeff > self._n_var:
  102. product = powers[0][exponent[0]]
  103. for index_var, power_var in enumerate(exponent[1:], 1):
  104. product *= powers[index_var][power_var]
  105. output += self._coeff[index_coeff] * product
  106. return output
  107. def __call__(self, *args, **kwargs):
  108. """Method for calling the Taylor expansion. Wraps several possibilities: evaluation on a point or a Cartesian
  109. product of intervals as well as composition with a map.
  110. Returns:
  111. Union[RealTaylorMap, Interval, numpy.ndarray]: Taylor expansion called on input.
  112. """
  113. if isinstance(args[0], RealTaylorMap):
  114. # composition
  115. return RealMultivarTaylor.compose(self, *args, **kwargs)
  116. if isinstance(args[0][0], Interval):
  117. # bounder
  118. return self.bounder(args[0])
  119. # evaluation
  120. return self.pointwise_eval(args[0])
  121. def __lt__(self, other: Union["RealMultivarTaylor", float]) -> bool:
  122. """Method enabling "<" inequality comparison with other Taylor expansions and scalars.
  123. Args:
  124. other (Union[RealMultivarTaylor, float])): quantity to be compared with.
  125. Returns:
  126. bool: if the input is a Taylor expansion in same algebra, compares coefficients pairwise
  127. (according to sorted monomials) until one is strictly smaller than the other. If the input is a
  128. scalar, performs comparison on Taylor expansion with only a constant part equal to it.
  129. """
  130. if isinstance(other, RealMultivarTaylor):
  131. if self.is_in_same_algebra(other):
  132. for coeff1, coeff2 in zip(self._coeff, other._coeff):
  133. if coeff1 != coeff2:
  134. return coeff1 < coeff2
  135. return False
  136. # scalar case
  137. return self < self.create_const_expansion(other)
  138. def __le__(self, other: Union["RealMultivarTaylor", float]) -> bool:
  139. """Method enabling "<=" inequality comparison with other Taylor expansions and scalars.
  140. Args:
  141. other (Union[RealMultivarTaylor, float])): quantity to be compared with.
  142. Returns:
  143. bool: returns logical union of "=" and "<".
  144. """
  145. return not self.__gt__(other)
  146. def __gt__(self, other: Union["RealMultivarTaylor", float]) -> bool:
  147. """Method enabling ">" inequality comparison with other Taylor expansions and scalars.
  148. Args:
  149. other (Union[RealMultivarTaylor, float])): quantity to be compared with.
  150. Returns:
  151. bool: if the input is a Taylor expansion in same algebra, compares coefficients pairwise
  152. (according to sorted monomials) until one is strictly greater than the other. If the input is a
  153. scalar, performs comparison on Taylor expansion with only a constant part equal to it.
  154. """
  155. if isinstance(other, RealMultivarTaylor):
  156. if self.is_in_same_algebra(other):
  157. for coeff1, coeff2 in zip(self._coeff, other._coeff):
  158. if coeff1 != coeff2:
  159. return coeff1 > coeff2
  160. return False
  161. # scalar case
  162. return self > self.create_const_expansion(other)
  163. def __ge__(self, other: Union["RealMultivarTaylor", float]) -> bool:
  164. """Method enabling ">=" inequality comparison with other Taylor expansions and scalars.
  165. Args:
  166. other (Union[RealMultivarTaylor, float])): quantity to be compared with.
  167. Returns:
  168. bool: returns logical union of "=" and ">".
  169. """
  170. return not self.__lt__(other)
  171. def __abs__(self) -> "RealMultivarTaylor":
  172. """Method implementation the absolute value for real Taylor expansions.
  173. Returns:
  174. RealMultivarTaylor: Taylor expansion composed with absolute value from the left hand side.
  175. """
  176. const = self.const
  177. if const == 0.:
  178. raise ValueError("The absolute value is not differentiable at zero.")
  179. return self.copy() if const > 0. else -self
  180. def __floor__(self) -> "RealMultivarTaylor":
  181. """Version for Taylor expansions of the floor function.
  182. Returns:
  183. RealMultivarTaylor: floor of Taylor expansion.
  184. """
  185. return self.create_const_expansion(math.floor(self.const))
  186. def __ceil__(self) -> "RealMultivarTaylor":
  187. """Version for Taylor expansions of the ceil function.
  188. Returns:
  189. RealMultivarTaylor: ceil of Taylor expansion.
  190. """
  191. return self.create_const_expansion(math.ceil(self.const))
  192. def sign(self) -> "RealMultivarTaylor":
  193. """Version for Taylor expansions of the sign function.
  194. Returns:
  195. RealMultivarTaylor: sign of Taylor expansion.
  196. """
  197. return self.create_const_expansion(np.sign(self.const))
  198. def cbrt(self) -> "RealMultivarTaylor":
  199. """Version for Taylor expansions of the cubic root function.
  200. Returns:
  201. RealMultivarTaylor: cubic root of Taylor expansion.
  202. """
  203. return self ** (1. / 3.)
  204. @staticmethod
  205. def seq_deriv_atan_atanh(c: float, eps: float, order: int) -> np.ndarray:
  206. csq = c * c
  207. cd = 2. * c
  208. aux = -1. / (csq + eps)
  209. seq = np.zeros(order)
  210. seq[0] = 1. / (1. + eps * csq)
  211. if order > 1:
  212. seq[1] = seq[0] * cd * aux
  213. for i in range(2, order):
  214. seq[i] = (seq[i - 2] + cd * seq[i - 1]) * aux
  215. return seq
  216. def arctan(self) -> "RealMultivarTaylor":
  217. """Version for Taylor expansions of the inverse tangent function. Here so that Numpy can use it on arrays.
  218. Returns:
  219. RealMultivarTaylor: inverse tangent of Taylor expansion.
  220. """
  221. if self.is_trivial():
  222. return self.create_const_expansion(math.atan(self.const))
  223. order = self.order
  224. const = self.const
  225. sequence = self.seq_deriv_atan_atanh(const, 1., order)
  226. seq = sequence / np.arange(1, order + 1)
  227. nilpo = self.get_nilpo_part()
  228. arctan = seq[-1] * nilpo
  229. for el in seq[-2::-1]:
  230. arctan.const = el
  231. arctan *= nilpo
  232. arctan.const = math.atan(const)
  233. return arctan
  234. def arctanh(self) -> "RealMultivarTaylor":
  235. """Version for Taylor expansions of the inverse hyperbolic tangent function.
  236. Here so that Numpy can use it on arrays.
  237. Returns:
  238. RealMultivarTaylor: inverse hyperbolic tangent of Taylor expansion.
  239. """
  240. if self.is_trivial():
  241. return self.create_const_expansion(math.atanh(self.const))
  242. order = self.order
  243. const = self.const
  244. sequence = self.seq_deriv_atan_atanh(const, -1., order)
  245. seq = sequence / np.arange(1, order + 1)
  246. nilpo = self.get_nilpo_part()
  247. arctanh = seq[-1] * nilpo
  248. for el in seq[-2::-1]:
  249. arctanh.const = el
  250. arctanh *= nilpo
  251. arctanh.const = math.atanh(const)
  252. return arctanh
  253. @staticmethod
  254. def seq_deriv_asin_asinh(c: float, eps: float, order: int) -> np.ndarray:
  255. csq = c * c
  256. aux = -1. / (csq + eps)
  257. seq = np.zeros(order)
  258. seq[0] = 1. / math.sqrt(1. + eps * csq)
  259. if order > 1:
  260. seq[1] = seq[0] * c * aux
  261. inter = aux / np.arange(2, order)
  262. for i, el in enumerate(inter, 2):
  263. seq[i] = ((i - 1.) * seq[i - 2] + c * (2. * i - 1.) * seq[i - 1]) * el
  264. return seq
  265. def arcsin(self) -> "RealMultivarTaylor":
  266. """Version for Taylor expansions of the inverse sine function.
  267. Returns:
  268. RealMultivarTaylor: inverse sine of Taylor expansion.
  269. """
  270. if self.is_trivial():
  271. return self.create_const_expansion(math.asin(self.const))
  272. order = self.order
  273. const = self.const
  274. sequence = self.seq_deriv_asin_asinh(const, -1., order)
  275. nilpo = self.get_nilpo_part()
  276. seq = sequence / np.arange(1, order + 1)
  277. arcsine = seq[-1] * nilpo
  278. for el in seq[-2::-1]:
  279. arcsine.const = el
  280. arcsine *= nilpo
  281. arcsine.const = math.asin(const)
  282. return arcsine
  283. def arccos(self) -> "RealMultivarTaylor":
  284. """Version for Taylor expansions of the inverse cosine function.
  285. Returns:
  286. RealMultivarTaylor: inverse cosine of Taylor expansion.
  287. """
  288. return -self.arcsin() + math.pi / 2.
  289. def arcsinh(self) -> "RealMultivarTaylor":
  290. """Version for Taylor expansions of the inverse hyperbolic sine function.
  291. Returns:
  292. RealMultivarTaylor: inverse hyperbolic sine of Taylor expansion.
  293. """
  294. if self.is_trivial():
  295. return self.create_const_expansion(math.asinh(self.const))
  296. order = self.order
  297. const = self.const
  298. sequence = self.seq_deriv_asin_asinh(const, 1., order)
  299. seq = sequence / np.arange(1, order + 1)
  300. nilpo = self.get_nilpo_part()
  301. arcsineh = seq[-1] * nilpo
  302. for el in seq[-2::-1]:
  303. arcsineh.const = el
  304. arcsineh *= nilpo
  305. arcsineh.const = math.asinh(const)
  306. return arcsineh
  307. def arccosh(self) -> "RealMultivarTaylor":
  308. """Version for Taylor expansions of the inverse hyperbolic cosine function.
  309. Returns:
  310. RealMultivarTaylor: inverse hyperbolic cosine of Taylor expansion.
  311. """
  312. if self.is_trivial():
  313. return self.create_const_expansion(math.acosh(self.const))
  314. order = self.order
  315. const = self.const
  316. c2 = const * const
  317. aux = 1. / (1. - c2)
  318. seq = np.zeros(order)
  319. seq[0] = 1. / math.sqrt(c2 - 1.)
  320. if order > 1:
  321. seq[1] = const * seq[0] * aux
  322. for i in range(2, order):
  323. seq[i] = ((i - 1.) * seq[i - 2] + (2. * i - 1.) * const * seq[i - 1]) * aux / i
  324. seq /= np.arange(1, order + 1)
  325. nilpo = self.get_nilpo_part()
  326. arccosineh = seq[-1] * nilpo
  327. for el in seq[-2::-1]:
  328. arccosineh.const = el
  329. arccosineh *= nilpo
  330. arccosineh.const = math.acosh(const)
  331. return arccosineh
  332. def arctan2(self, q: Union["RealMultivarTaylor", float]) -> "RealMultivarTaylor":
  333. """Version for Taylor expansions of the arctan2.
  334. Args:
  335. Union["RealMultivarTaylor", float]: second argument of arctan2
  336. Returns:
  337. RealMultivarTaylor: arctan2(self, q).
  338. """
  339. if q != 0.:
  340. output = (self / q).arctan()
  341. elif self.const != 0.:
  342. output = -(q / self).arctan()
  343. else:
  344. raise ValueError
  345. output.const = math.atan2(self.const, float(q))
  346. return output