taylor_map.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. # taylor_map.py: range of classes implementing maps of Taylor expansion
  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, Optional, Iterable, Union
  15. import numpy as np
  16. from numba import njit
  17. from swiftt.taylor.complex_multivar_taylor import ComplexMultivarTaylor, Scalar
  18. from swiftt.taylor.taylor_expans_abstract import TaylorExpansAbstract, landau_symbol, default_inverse_name
  19. from swiftt.taylor.tables import algebra_dim
  20. from swiftt.interval import Interval
  21. from swiftt.map_abstract import MapAbstract
  22. class ComplexTaylorMap(TaylorExpansAbstract, MapAbstract):
  23. """Class for so-called Taylor map i.e. vectors of Taylor expansions in same algebra (same order and
  24. number of variables).
  25. Attributes:
  26. _coeff (numpy.ndarray): 2-D array grouping all the polynomial coefficients of the various map's
  27. elements (columns are along the algebra's dimension). Allows for faster manipulation.
  28. _var_type (type): type of coefficients (and implicitly the variables).
  29. """
  30. # intrinsic functions for arrays
  31. _sqrt_cst = np.sqrt
  32. _log_cst, _exp_cst = np.log, np.exp
  33. _cos_cst, _sin_cst = np.cos, np.sin
  34. _cosh_cst, _sinh_cst = np.cosh, np.sinh
  35. def __init__(self, expansions) -> None:
  36. """Constructor for Taylor maps.
  37. Args:
  38. expansions (List[ComplexMultivarTaylor}): Taylor expansions forming the map. Order is preserved.
  39. """
  40. MapAbstract.__init__(self, expansions)
  41. self._n_var = expansions[0].n_var
  42. self._order = expansions[0].order
  43. self._dim_alg = expansions[0].dim_alg
  44. self._var_type = expansions[0].var_type
  45. self._coeff = np.empty((self._len, self._dim_alg), dtype=self._var_type)
  46. for i, expansion in enumerate(expansions):
  47. self._coeff[i, :] = expansion.coeff
  48. @property
  49. def remainder_term(self) -> str:
  50. return self[0].remainder_term
  51. @property
  52. def const(self) -> np.ndarray:
  53. """
  54. Getter for the so-called constant coefficients i.e. associated to the zeroth-order contribution.
  55. Returns:
  56. Union[float, complex]: constants.
  57. """
  58. return np.array(self._coeff[:, 0])
  59. @const.setter
  60. def const(self, cst: Union[complex, float, np.ndarray]) -> None:
  61. """
  62. Setter for the so-called constant coefficients i.e. associated to the zeroth-order contribution.
  63. If a scalar is given, it replaces all the constant coefficients.
  64. Args:
  65. Union[float, complex]: new constants.
  66. """
  67. if isinstance(cst, (complex, float)):
  68. self.const = np.full(self._len, cst)
  69. elif len(cst) != self._len:
  70. raise ValueError("Inconsistent number of constants given for this map (" +
  71. str(len(cst)) + " instead of " + str(self._len) + ")")
  72. else:
  73. for i, el in enumerate(cst):
  74. self[i].const = el
  75. self._coeff[:, 0] = np.array(cst, dtype=self._var_type)
  76. @TaylorExpansAbstract.order.setter
  77. def order(self, new_order: int) -> None:
  78. if new_order != self._order:
  79. for i in range(0, self._len):
  80. self[i].order = new_order
  81. self._order = new_order
  82. self._dim_alg = self[0].dim_alg
  83. @TaylorExpansAbstract.n_var.setter
  84. def n_var(self, n_var: int) -> None:
  85. if n_var != self._n_var:
  86. for i in range(0, self._len):
  87. self[i].n_var = n_var
  88. self._n_var = n_var
  89. self._dim_alg = self[0].dim_alg
  90. @property
  91. def coeff(self) -> np.ndarray:
  92. return np.array(self._coeff)
  93. @coeff.setter
  94. def coeff(self, coefficients: np.ndarray) -> None:
  95. if coefficients.shape == (self._len, self._dim_alg):
  96. self._coeff = np.array(coefficients, dtype=self._var_type)
  97. else:
  98. raise ValueError("The given coefficients do not match the size of the map.")
  99. def __setitem__(self, item: int, expansion: ComplexMultivarTaylor) -> None:
  100. if not self.is_in_same_algebra(expansion):
  101. raise ValueError("Input expansion is not in the same algebra than the Taylor map.")
  102. self._coeff[item, :] = expansion.coeff
  103. MapAbstract.__setitem__(self, item, expansion)
  104. def __neg__(self) -> "ComplexTaylorMap":
  105. """Method defining negation (additive inverse). Overwrites parent implementation for performance.
  106. Returns:
  107. ComplexTaylorMap: opposite of object (from the point of view of addition).
  108. """
  109. return self.create_map_with_coeff(-self._coeff)
  110. def create_map_with_coeff(self, new_coeff: np.ndarray) -> "ComplexTaylorMap":
  111. """Method to create a new Taylor map (in same algebra and with same size than self) from given coefficients.
  112. Args:
  113. new_coeff (numpy.ndarray): coefficients of new map. Rows are for each map's elements and columns
  114. for monomials.
  115. Returns:
  116. ComplexTaylorMap: Taylor map corresponding to inputted coefficients.
  117. """
  118. return self.__class__([self[0].create_expansion_with_coeff(new_coeff[i, :]) for i in range(0, self._len)])
  119. def create_const_expansion(self, const: Iterable[Scalar]) -> "ComplexTaylorMap":
  120. """Method to create a Taylor map (in same algebra and with same size than self) whose polynomial part is
  121. constant.
  122. Args:
  123. const (Iterable[Scalar]): constants for each element.
  124. Returns:
  125. ComplexTaylorMap: Taylor map corresponding to inputted coefficients.
  126. """
  127. new_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  128. new_coeff[:, 0] = np.array(const)
  129. return self.create_map_with_coeff(new_coeff)
  130. def create_null_map(self) -> "ComplexTaylorMap":
  131. """Method to create a Taylor map (in same algebra and with same size than self) whose polynomial part is
  132. zero.
  133. Returns:
  134. ComplexTaylorMap: null Taylor map of self size and algebra.
  135. """
  136. new_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  137. return self.create_map_with_coeff(new_coeff)
  138. @TaylorExpansAbstract.var_names.setter
  139. def var_names(self, var_names: List[str]) -> None:
  140. """Setter for the name of the variables. Overwrites parent implementation to extend the new names to all
  141. elements of the map.
  142. Args:
  143. List[str]: name of the variables.
  144. """
  145. for i in range(0, self._len):
  146. self[i].var_names = var_names
  147. def is_univar(self) -> bool:
  148. """Method returning True if the map is made of univariate Taylor expansions, False otherwise.
  149. Returns:
  150. (bool): True if the map is univariate.
  151. """
  152. return self._n_var == 1
  153. def is_square_sized(self) -> bool:
  154. """Method returning True if the map has as many elements than variables, False otherwise.
  155. Returns:
  156. (bool): True if the map is square (as many variables as components).
  157. """
  158. return self._n_var == self._len
  159. def is_const(self) -> bool:
  160. """Method returning True if the polynomial part of all the map's elements are constant, False otherwise.
  161. Returns:
  162. (bool): True if and only if all non-constant coefficients are zero.
  163. """
  164. for expansion in self:
  165. if not expansion.is_const():
  166. return False
  167. return True
  168. def is_nilpotent(self) -> bool:
  169. """Method returning True if all the map's elements are nilpotent, False otherwise.
  170. Returns:
  171. (bool): True if the map is nilpotent.
  172. """
  173. for i in range(0, self._len):
  174. if self._coeff[i, 0] != 0.:
  175. return False
  176. return True
  177. def get_nilpo_part(self) -> "ComplexTaylorMap":
  178. """Method returning the nilpotent part of the Taylor map.
  179. Returns:
  180. ComplexTaylorMap: nilpotent part of Taylor map.
  181. """
  182. new_coeff = np.array(self._coeff, dtype=self._var_type)
  183. new_coeff[:, 0] = 0.
  184. return self.create_map_with_coeff(new_coeff)
  185. def get_linear_part(self) -> "ComplexTaylorMap":
  186. """Method returning the linear part of the Taylor map.
  187. Returns:
  188. ComplexTaylorMap: linear part of Taylor map.
  189. """
  190. try:
  191. n_var_plus_one = self._n_var + 1
  192. new_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  193. new_coeff[:, :n_var_plus_one] += self._coeff[:, :n_var_plus_one]
  194. return self.create_map_with_coeff(new_coeff)
  195. except IndexError:
  196. raise ValueError("There is no linear part in a zeroth-order expansion.")
  197. def get_high_order_part(self, order: int) -> "ComplexTaylorMap":
  198. """Method returning the high order part of the map in the same algebra. It is not a rigorous operation.
  199. Args:
  200. order (int): order (included) below which all contributions are removed.
  201. Returns:
  202. ComplexTaylorMap: high order part of the Taylor map.
  203. """
  204. if 1 <= order <= self._order:
  205. map_coeff = np.array(self._coeff, dtype=self._var_type)
  206. map_coeff[:, :algebra_dim(self._n_var, order - 1)] = 0.
  207. return self.create_map_with_coeff(map_coeff)
  208. raise ValueError("The inputted order exceeds the current order.")
  209. def get_low_order_part(self, order: int) -> "ComplexTaylorMap":
  210. """Method returning the low order part of the map in the same algebra. It is not a rigorous operation.
  211. Args:
  212. order (int): order (included) above which all contributions are removed.
  213. Returns:
  214. ComplexTaylorMap: low order part of the Taylor map.
  215. """
  216. if order == 0:
  217. return self.get_const_part()
  218. if order < self._order:
  219. map_coeff = np.array(self._coeff, dtype=self._var_type)
  220. map_coeff[:, algebra_dim(self._n_var, order):] = 0.
  221. return self.create_map_with_coeff(map_coeff)
  222. raise ValueError("The inputted order exceeds the current order.")
  223. def get_low_order_wrt_var(self, index_var: int, order: int) -> "ComplexTaylorMap":
  224. new_coeff = self.coeff
  225. mapping = self[0].get_mapping_monom()
  226. indices_coeff = [mapping[exponent] for exponent in mapping if exponent[index_var] > order]
  227. new_coeff[:, indices_coeff] = 0.
  228. return self.create_map_with_coeff(new_coeff)
  229. def linearly_combine_with_another(self, alpha: Scalar, expansion: "ComplexTaylorMap",
  230. beta: Scalar) -> "ComplexTaylorMap":
  231. """Method multiplying with a scalar and then adding with a another expansion also multiplied by some scalar.
  232. Overwritten for speed purposes.
  233. Args:
  234. alpha (Union[complex, float]): multiplier for self.
  235. expansion (ComplexMultivarTaylor): expansion to be linearly combined with.
  236. beta (Union[complex, float]): multiplier for other expansion.
  237. Returns:
  238. (ComplexMultivarTaylor): linear combination of self and arguments.
  239. """
  240. return self.create_map_with_coeff(alpha * self._coeff + beta * expansion.coeff)
  241. def deriv_once_wrt_var(self, index_var: int) -> "ComplexTaylorMap":
  242. """Method performing differentiation with respect to a given unknown variable while remaining in the same
  243. algebra. This transformation is not rigorous in the sense that the order of the map should be decreased by
  244. one.
  245. Args:
  246. index_var (int): variable number w.r.t. which differentiation needs to be performed.
  247. Returns:
  248. ComplexTaylorMap: differentiated Taylor map w.r.t. input variable number.
  249. """
  250. if self.is_trivial():
  251. return self.create_null_map()
  252. # order of at least one
  253. new_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  254. if self.is_univar():
  255. new_coeff[:, :-1] = self._coeff[:, 1:] * np.arange(1, self.dim_alg)
  256. else:
  257. # multivariate case
  258. nb = algebra_dim(self._n_var, self._order - 1)
  259. links = self[0].get_table_deriv()[:nb, index_var]
  260. integers = np.array(list(self[0].get_mapping_monom().keys()))[links, index_var]
  261. new_coeff[:, :nb] = self._coeff[:, links] * integers
  262. return self.create_map_with_coeff(new_coeff)
  263. def integ_once_wrt_var(self, index_var: int) -> "ComplexTaylorMap":
  264. """Method performing integration with respect to a given unknown variable while remaining in the same
  265. algebra. The integrations constant are zero. This transformation is not rigorous in the sense that the order of
  266. the map should be increased by one.
  267. Args:
  268. index_var (int): variable number w.r.t. which integration needs to be performed.
  269. Returns:
  270. ComplexTaylorMap: integrated Taylor map w.r.t. input variable number.
  271. """
  272. new_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  273. if self.is_univar():
  274. new_coeff[:, 1:] = self._coeff[:, :-1] / np.arange(1, self.dim_alg)
  275. else:
  276. # multivariate case
  277. nb = algebra_dim(self._n_var, self._order - 1)
  278. weights = np.array(list(self[0].get_mapping_monom().keys()))[:nb, index_var] + 1.
  279. new_coeff[:, self[0].get_table_deriv()[:nb, index_var]] = self._coeff[:, :nb] / weights
  280. return self.create_map_with_coeff(new_coeff)
  281. def rigorous_integ_once_wrt_var(self, index_var: int) -> "ComplexTaylorMap":
  282. """Method performing integration with respect to a given unknown variable. The integration constants are zero.
  283. This transformation is rigorous as the order of the map is increased by one. In other words, the output
  284. lives in another algebra, of higher dimension.
  285. Args:
  286. index_var (int): variable number w.r.t. which integration needs to be performed.
  287. Returns:
  288. ComplexTaylorMap: integrated Taylor map w.r.t. input variable number.
  289. """
  290. new_coeff = np.zeros((self._len, algebra_dim(self._n_var, self._order + 1)),
  291. dtype=self._var_type)
  292. if self.is_univar():
  293. new_expansions = [self[0].__class__(self._order + 1, self[0].var_names)]
  294. new_coeff[:, 1:] = self._coeff / np.arange(1, self.order + 2)
  295. else:
  296. # multivariate case
  297. new_expansions = [self[0].__class__(self._n_var, self._order + 1, self[0].var_names)]
  298. inv_exponents_plus_one = 1. / np.arange(1., self._order + 2)
  299. new_mapping = new_expansions[0].get_mapping_monom()
  300. for exponent, index_coeff in self[0].get_mapping_monom().items():
  301. new_tuple = exponent[:index_var] + (exponent[index_var] + 1,) + exponent[index_var + 1:]
  302. new_coeff[:, new_mapping[new_tuple]] = self._coeff[:, index_coeff] * \
  303. inv_exponents_plus_one[exponent[index_var]]
  304. new_expansions[0].coeff = new_coeff[0, :]
  305. for i in range(1, self._len):
  306. new_expansions.append(new_expansions[0].create_expansion_with_coeff(new_coeff[i, :]))
  307. return self.__class__(new_expansions)
  308. def truncated(self, new_order: int) -> "ComplexTaylorMap":
  309. """Method for the truncation at a given order. Output lives in another algebra, of lower dimension.
  310. Args:
  311. new_order (int): order of algebra in which to truncate the Taylor map.
  312. Returns:
  313. ComplexTaylorMap: Taylor map truncated at input order.
  314. """
  315. truncated = [self[0].truncated(new_order)]
  316. new_dim = truncated[0].dim_alg
  317. for i in range(1, self._len):
  318. truncated.append(truncated[0].create_expansion_with_coeff(self._coeff[i, :new_dim]))
  319. return self.__class__(truncated)
  320. def prolong(self, new_order: int) -> "ComplexTaylorMap":
  321. """Method for the prolongation (opposite of truncation) at a given order. Output lives in another algebra, of
  322. higher dimension. It is not a rigorous operation as the possible contributions within the former remainder are
  323. ignored.
  324. Args:
  325. new_order (int): order of algebra in which to prolong the Taylor map.
  326. Returns:
  327. ComplexTaylorMap: Taylor expansion prolonged at input order.
  328. """
  329. prolonged = [self[0].prolong(new_order)]
  330. new_coeff = np.zeros(prolonged[0].dim_alg)
  331. for i in range(1, self._len):
  332. new_coeff[:self.dim_alg] = np.array(self._coeff[i, :], dtype=self._var_type)
  333. prolonged.append(prolonged[0].create_expansion_with_coeff(new_coeff))
  334. return self.__class__(prolonged)
  335. def __add__(self, other) -> "ComplexTaylorMap":
  336. """Method defining right-hand side addition. Works both with scalars and expansions (element-wise) as well as
  337. with other maps.
  338. Args:
  339. other (Union[ComplexTaylorMap, ComplexMultivarTaylor, complex, float]): quantity to be added.
  340. Returns:
  341. ComplexTaylorMap: Taylor map summed with argument.
  342. """
  343. if isinstance(other, ComplexTaylorMap):
  344. map_coeff = self._coeff + other._coeff
  345. elif isinstance(other, ComplexMultivarTaylor):
  346. map_coeff = self._coeff + other.coeff
  347. else:
  348. # scalar case
  349. map_coeff = np.array(self._coeff, dtype=self._var_type)
  350. map_coeff[:, 0] += other
  351. return self.create_map_with_coeff(map_coeff)
  352. def __sub__(self, other) -> "ComplexTaylorMap":
  353. """Method defining right-hand side subtraction. Works both with scalars and expansions (element-wise) as well as
  354. with other maps.
  355. Args:
  356. other (Union[ComplexTaylorMap, ComplexMultivarTaylor, complex, float]): quantity to be subtracted.
  357. Returns:
  358. ComplexTaylorMap: Taylor map subtracted with argument.
  359. """
  360. if isinstance(other, ComplexTaylorMap):
  361. map_coeff = self._coeff - other._coeff
  362. elif isinstance(other, ComplexMultivarTaylor):
  363. map_coeff = self._coeff - other.coeff
  364. else:
  365. # scalar case
  366. map_coeff = np.array(self._coeff, dtype=self._var_type)
  367. map_coeff[:, 0] -= other
  368. return self.create_map_with_coeff(map_coeff)
  369. def __rsub__(self, other) -> "ComplexTaylorMap":
  370. """Method defining left-hand side addition. Works both with scalars and expansions (element-wise) as well as
  371. with other maps.
  372. Args:
  373. other (Union[ComplexTaylorMap, ComplexMultivarTaylor, complex, float]): quantity to perfect subtraction on.
  374. Returns:
  375. ComplexTaylorMap: argument subtracted with Taylor map.
  376. """
  377. if isinstance(other, ComplexTaylorMap):
  378. map_coeff = -self._coeff + other._coeff
  379. elif isinstance(other, ComplexMultivarTaylor):
  380. map_coeff = -self._coeff + other.coeff
  381. else:
  382. # scalar case
  383. map_coeff = -self._coeff
  384. map_coeff[:, 0] += other
  385. return self.create_map_with_coeff(map_coeff)
  386. def __eq__(self, other) -> bool:
  387. """Method enabling comparison with other Taylor maps as well as vectors.
  388. Returns:
  389. (bool): True if the elements of the argument are all equal (pair-wise) with the ones of this map.
  390. """
  391. if self._len != len(other):
  392. return False
  393. # objects have same number of elements, so now compare them pair-wise
  394. for expans1, expans2 in zip(self, other):
  395. if expans1 != expans2:
  396. return False
  397. return True
  398. def pointwise_eval(self, x: np.ndarray) -> np.ndarray:
  399. """Method for the evaluation of the Taylor map on a given point.
  400. Args:
  401. x (numpy.ndarray): point of evaluation.
  402. Returns:
  403. (numpy.ndarray): Taylor map evaluated at given point.
  404. """
  405. if self.is_trivial():
  406. return self.const
  407. if self.is_univar():
  408. # Horner's scheme
  409. output = self._coeff[:, -1] * x + self._coeff[:, -2]
  410. for i in range(self.dim_alg - 3, -1, -1):
  411. output = output * x + self._coeff[:, i]
  412. return output
  413. # multivariate case with order at least one
  414. mapping = self[0].get_mapping_monom()
  415. products = np.ones(self.dim_alg, dtype=self._var_type)
  416. for exponent, index_coeff in mapping.items():
  417. for index_var, power_var in enumerate(exponent):
  418. if power_var > 0:
  419. new_tuple = exponent[:index_var] + (power_var - 1,) + exponent[index_var + 1:]
  420. products[index_coeff] = x[index_var] * products[mapping[new_tuple]]
  421. break
  422. return self._coeff.dot(products)
  423. def massive_eval(self, Xs: np.ndarray) -> np.ndarray:
  424. """Method for the evaluation of the Taylor map on a range of points (vectorized evaluation).
  425. Args:
  426. Xs (numpy.ndarray): points of evaluation.
  427. Returns:
  428. (numpy.ndarray): Taylor map evaluated at given points.
  429. """
  430. if self.is_univar():
  431. return np.transpose(np.array([expansion.massive_eval(Xs) for expansion in self], dtype=self._var_type))
  432. if Xs.shape[1] != self.n_var and len(Xs.shape) == 2:
  433. raise IndexError("The number of columns of the input should equal the number of variables in the "
  434. "expansion.")
  435. if self.is_trivial():
  436. return np.array([self.const] * Xs.shape[0], dtype=self._var_type)
  437. # multivariate case of order at least two
  438. products = np.ones((self.dim_alg, Xs.shape[0]), dtype=self._var_type)
  439. mapping = self[0].get_mapping_monom()
  440. for exponent, index_coeff in mapping.items():
  441. for index_var, power_var in enumerate(exponent):
  442. if power_var > 0:
  443. new_tuple = exponent[:index_var] + (power_var - 1,) + exponent[index_var + 1:]
  444. products[index_coeff, :] = Xs[:, index_var] * products[mapping[new_tuple], :]
  445. break
  446. return np.transpose(self._coeff @ products)
  447. def __call__(self, *args, **kwargs):
  448. """Method for calling the Taylor expansion. Wraps several possibilities: evaluation and composition with a map.
  449. Returns:
  450. (Union[ComplexTaylorMap, numpy.ndarray]): Taylor map called on input.
  451. """
  452. other = args[0]
  453. return self.compose(other) if isinstance(other, self.__class__) else self.pointwise_eval(other)
  454. def compose(self, other: "ComplexTaylorMap") -> "ComplexTaylorMap":
  455. """Method performing composition with inputted Taylor map (must have the same order and as many elements as
  456. self has variables). The result is another map and has the same variables than the input.
  457. Args:
  458. other (ComplexTaylorMap): Taylor map to be composed on the right-hand side.
  459. Returns:
  460. ComplexTaylorMap: composed map.
  461. """
  462. if other.order != self._order or len(other) != self._n_var:
  463. raise ValueError("Inconsistent maps for composition")
  464. if not other.is_nilpotent():
  465. raise ValueError("Right-hand-side map must be nilpotent")
  466. if self.is_univar() and len(other) == 1:
  467. return self.__class__([self[0].compose(other[0])])
  468. if self.is_trivial():
  469. return other.create_const_expansion(self.const)
  470. # order of at least one
  471. rhs_coeff = np.zeros((self.dim_alg, other[0].dim_alg), dtype=self._var_type)
  472. products = [other[0].create_const_expansion(1.)]
  473. mapping = self[0].get_mapping_monom()
  474. for exponent, index_coeff in mapping.items():
  475. for index_var, power_var in enumerate(exponent):
  476. if power_var > 0:
  477. new_tuple = exponent[:index_var] + (power_var - 1,) + exponent[index_var + 1:]
  478. products.append(other[index_var] * products[mapping[new_tuple]])
  479. rhs_coeff[index_coeff, :] = products[index_coeff].coeff
  480. break
  481. else: # no break
  482. rhs_coeff[0, 0] = 1.
  483. new_coeff = self._coeff @ rhs_coeff
  484. return self.__class__([other[0].create_expansion_with_coeff(new_coeff[i, :]) for i in range(0, self._len)])
  485. def _compo_inverse_lin_part(self) -> "ComplexTaylorMap":
  486. """Method returning the inverse of the linear part of the Taylor map from the point of view of composition. It
  487. boils down to inverting the square matrix made of all the corresponding coefficients.
  488. Returns:
  489. ComplexTaylorMap: inverse of linear part of Taylor expansion.
  490. """
  491. lin_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  492. lin_coeff[:, 1:self._len+1] = np.linalg.inv(self.jacobian)
  493. return self.create_map_with_coeff(lin_coeff)
  494. def compo_inverse(self, names_inverse: Optional[List[str]] = None) -> "ComplexTaylorMap":
  495. """Method returning the inverse of the Taylor map from the point of view of composition, i.e.
  496. so that composed with self it gives the identity map (X1, X2, ...). The computation is done iteratively,
  497. starting from the inversion of the first-order truncated map. See the work of M. Berz.
  498. Args:
  499. names_inverse (List[str]): name of inverse variables.
  500. Returns:
  501. ComplexTaylorMap: composition-inverse of Taylor expansion.
  502. """
  503. if not self.is_square_sized():
  504. raise ValueError("Right-hand-side size must equal left-hand-side\'s number of variables")
  505. if not self.is_nilpotent():
  506. raise ValueError("Map to be inverted cannot have non-zero constant terms")
  507. if self._len == 1:
  508. return self.__class__([self[0].compo_inverse(names_inverse)])
  509. if names_inverse is None:
  510. names_inverse = TaylorExpansAbstract.\
  511. get_default_var_names(self._n_var, default_inverse_name)
  512. inv_lin_map = self._compo_inverse_lin_part()
  513. inv_lin_map.var_names = names_inverse
  514. if self._order == 1:
  515. return inv_lin_map
  516. # order is at least two
  517. inter = -self.get_nonlin_part()
  518. ident = self.create_id_map()
  519. inverted_map = inv_lin_map.compose(inter.compose(inv_lin_map) + ident)
  520. for __ in range(2, self._order):
  521. inverted_map = inv_lin_map.compose(inter.compose(inverted_map) + ident)
  522. return inverted_map
  523. def create_id_map(self) -> "ComplexTaylorMap":
  524. """Method returning the so-called identity map of the algebra. If the variables' names are x1, ... xN, the i-th
  525. element of the map is xi + remainder.
  526. Returns:
  527. ComplexTaylorMap: identity map for this order and number of variables.
  528. """
  529. if not self.is_square_sized():
  530. raise ValueError("This map is not squared thus an identity map cannot be derived")
  531. id_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  532. id_coeff[:, 1:self._len+1] = np.eye(self._len)
  533. return self.create_map_with_coeff(id_coeff)
  534. @property
  535. def jacobian(self) -> np.ndarray:
  536. """Method returning the evaluation of the first-order derivatives w.r.t. all variables of all map's components.
  537. Returns:
  538. (numpy.ndarray): first-order derivatives in 2-D array form.
  539. """
  540. return np.array(self._coeff[:self.n_var, 1:self.n_var+1], dtype=self._var_type)
  541. def dot(self, other) -> ComplexMultivarTaylor:
  542. if not isinstance(other, self.__class__):
  543. new_coeff = other.dot(self._coeff)
  544. return self[0].create_expansion_with_coeff(new_coeff)
  545. return MapAbstract.dot(self, other)
  546. def divided_by_var(self, index_var: int) -> "ComplexTaylorMap":
  547. """Method returning the Taylor map divided by the input variable. This is not a rigorous operation as the
  548. order should be decreased by one.
  549. Args:
  550. index_var (str): index of variable to divide with.
  551. Returns:
  552. ComplexTaylorMap: Taylor expansion divided by variable.
  553. """
  554. return self.__class__([expansion.divided_by_var(index_var) for expansion in self])
  555. def var_eval(self, index_var: int, value: Scalar) -> "ComplexTaylorMap":
  556. """Method returning a Taylor map where a variable has been replaced by a fixed scalar value. In other
  557. words, it is a partial evaluation of the polynomial part. It is not rigorous as terms of higher order hidden in
  558. the remainder would need to be considered in this operation.
  559. Args:
  560. index_var (int): index of variable to be evaluated.
  561. value (Union[complex, float]): value to replace given variable.
  562. Returns:
  563. ComplexTaylorMap: Taylor map with removed dependency.
  564. """
  565. powers = np.cumprod(value * np.ones(self._order, dtype=self._var_type))
  566. new_coeff = np.zeros((self._len, self.dim_alg), dtype=self._var_type)
  567. mapping = self[0].get_mapping_monom()
  568. tuple_0 = (0,)
  569. for exponent, index_coeff in mapping.items():
  570. if exponent[index_var] == 0:
  571. new_coeff[:, index_coeff] += self._coeff[:, index_coeff]
  572. else:
  573. new_exponent = exponent[:index_var] + tuple_0 + exponent[index_var + 1:]
  574. new_coeff[:, mapping[new_exponent]] += self._coeff[:, index_coeff] * powers[exponent[index_var] - 1]
  575. return self.create_map_with_coeff(new_coeff)
  576. def contrib_removed(self, indices_var: List[int]) -> "ComplexTaylorMap":
  577. """Method returning a Taylor map where all coefficients associated to input variables' indices are set to
  578. zero.
  579. Args:
  580. indices_var (Iterable[int]): indices of variables whose contribution is to be removed.
  581. Returns:
  582. ComplexTaylorMap: Taylor map with removed contributions.
  583. """
  584. new_coeff = np.array(self._coeff, dtype=self._var_type)
  585. try:
  586. exponents = np.array(list(self[0].get_mapping_monom().keys()))[:, indices_var]
  587. except TypeError:
  588. raise ValueError("At least one inputted variable index is not an integer")
  589. except IndexError:
  590. raise ValueError("At least one inputted variable index does not exist in this algebra")
  591. for i in range(0, len(indices_var)):
  592. new_coeff[:, exponents[:, i] != 0] = 0.
  593. return self.create_map_with_coeff(new_coeff)
  594. def var_removed(self, index_var: int) -> "ComplexTaylorMap":
  595. """Method for the removal of a variable. Output lives in another algebra, of smaller dimension. All its
  596. terms associated with the old variables only are identical to original expansion.
  597. Args:
  598. index_var (int): index of variable to be removed.
  599. Returns:
  600. ComplexTaylorMap: Taylor map with a variable removed.
  601. """
  602. return self.__class__([expansion.var_removed(index_var) for expansion in self])
  603. def var_inserted(self, index_new_var: int, unknown_name: Optional[str] = None) -> "ComplexTaylorMap":
  604. """Method for the addition of a new variable. Output lives in another algebra, of higher dimension. All its
  605. terms associated with the new variable are zero and the other ones are identical to original expansion.
  606. Args:
  607. index_new_var (int): index of new variable to be added.
  608. unknown_name (str): name of new variable.
  609. Returns:
  610. ComplexTaylorMap: Taylor expansion with an additional variable.
  611. """
  612. return self.__class__([expansion.var_inserted(index_new_var, unknown_name) for expansion in self])
  613. def pow2(self) -> "ComplexTaylorMap":
  614. """Method to raise Taylor map to the power 2 i.e. compute its multiplicative square.
  615. Returns:
  616. ComplexTaylorMap: Taylor map raised to power 2.
  617. """
  618. return self.__class__([expansion.pow2() for expansion in self])
  619. @staticmethod
  620. @njit(cache=True)
  621. def mul_map_expans(coeff_map: np.ndarray, other_coeff: np.ndarray, square_indices: np.ndarray,
  622. table_mul: np.ndarray, indices_mul: np.ndarray) -> np.ndarray:
  623. """Static method transforming series of coefficients of a map and a single Taylor expansion into the
  624. coefficients of the map from their multiplicative product. Method is static for just-in-time compiling
  625. with Numba.
  626. Args:
  627. coeff_map (numpy.ndarray): coefficients from the Taylor map.
  628. other_coeff (numpy.ndarray): coefficients of the Taylor expansion.
  629. square_indices (numpy.ndarray): precomputed indices corresponding to monomials which are the square
  630. of another monomial in the algebra.
  631. table_mul (numpy.ndarray): flattened algebra's multiplication table.
  632. indices_mul (numpy.ndarray): algebra's multiplication indices.
  633. Returns:
  634. (numpy.ndarray): coefficient corresponding to product.
  635. """
  636. multiplied_coeff = np.outer(coeff_map[:, 0], other_coeff) + other_coeff[0] * coeff_map
  637. dim_half_order = len(square_indices)
  638. symmetric_terms = coeff_map[:, :dim_half_order] * other_coeff[:dim_half_order]
  639. multiplied_coeff[:, 0] = 0.
  640. multiplied_coeff[:, square_indices] += symmetric_terms
  641. slices = indices_mul[2:] - indices_mul[1:-1]
  642. for i, (slice_index, el) in enumerate(zip(slices, other_coeff[2:]), 2):
  643. multiplied_coeff[:, table_mul[indices_mul[i - 1] + 1:indices_mul[i]]] += np.outer(coeff_map[:, i], other_coeff[1:slice_index]) \
  644. + el * coeff_map[:, 1:slice_index]
  645. return multiplied_coeff
  646. def __mul__(self, other) -> "ComplexTaylorMap":
  647. """Method defining right-hand side multiplication. It works for the external multiplication i.e. with scalars
  648. and for the internal one with an expansion (element-wise too).
  649. Args:
  650. other (Union[ComplexMultivarTaylor, Iterable[ComplexMultivarTaylor, complex, float], complex, float]):
  651. quantity to be multiplied with.
  652. Returns:
  653. ComplexTaylorMap: multiplied objects.
  654. """
  655. if isinstance(other, ComplexMultivarTaylor):
  656. multiplied_coeff = self.mul_map_expans(self._coeff, other.coeff, self[0].get_square_indices(),
  657. self[0].get_flat_table_mul(), self[0].get_indices_mul())
  658. return self.create_map_with_coeff(multiplied_coeff)
  659. if isinstance(other, (complex, float)):
  660. return self.create_map_with_coeff(self._coeff * other)
  661. try:
  662. if len(other) == self._len: # component-wise multiplication
  663. if isinstance(other[0], ComplexMultivarTaylor):
  664. return self.__class__([el1 * el2 for el1, el2 in zip(self, other)])
  665. return self.create_map_with_coeff(np.einsum("ji,j->ji", self._coeff, other))
  666. except TypeError:
  667. pass
  668. raise ValueError
  669. def create_map_from_smaller_algebra(self, other: "ComplexTaylorMap") -> "ComplexTaylorMap":
  670. """Method to create a Taylor map in same algebra than self, from a map with same size but in another algebra
  671. with same order but less variables. Names of intersecting variables must be identical otherwise the function
  672. does not work.
  673. Args:
  674. other (ComplexTaylorMap): Taylor expansion to extend in current algebra.
  675. Returns:
  676. ComplexTaylorMap: Taylor map whose polynomial coefficients are all zero except the ones related only to
  677. the variables of the input that are then identical to them.
  678. """
  679. if other.order != self._order or other.n_var >= self._n_var:
  680. raise ValueError("The inputted map has a different order.")
  681. expansion_coeff = other.coeff
  682. new_coeff = np.zeros((len(other), self._dim_alg), dtype=self._var_type)
  683. coeff_old_algebra = np.zeros(other.dim_alg, dtype=self._var_type)
  684. if other[0].n_var == 1:
  685. # the monomials-coefficients mapping is trivial in the univariate case (hence no dedicated function)
  686. old_mapping = {(j,): j for j in range(0, other[0].order + 1)}
  687. else:
  688. # multivariate case
  689. old_mapping = other[0].get_mapping_monom()
  690. for old_exponent, old_index_var in old_mapping.items():
  691. coeff_old_algebra[:old_index_var] = 0.
  692. coeff_old_algebra[old_index_var] = 1.
  693. old_monomial = other[0].create_expansion_with_coeff(coeff_old_algebra)
  694. str_old_monomial = str(old_monomial).split(landau_symbol)[0]
  695. coeff_new_algebra = np.zeros(self._dim_alg, dtype=self._var_type)
  696. for new_exponent, new_index_var in self[0].get_mapping_monom().items():
  697. coeff_new_algebra[:new_index_var] = 0.
  698. if sum(new_exponent) == sum(old_exponent):
  699. coeff_new_algebra[new_index_var] = 1.
  700. str_new_monomial = str(self[0].create_expansion_with_coeff(coeff_new_algebra)).split(landau_symbol)[0]
  701. if str_new_monomial == str_old_monomial:
  702. new_coeff[:, new_index_var] = expansion_coeff[:, old_index_var]
  703. break
  704. else: # no break
  705. raise ValueError
  706. return self.__class__([self[0].create_expansion_with_coeff(new_coeff[i, :]) for i in range(0, len(other))])
  707. def tan(self) -> "ComplexTaylorMap":
  708. return ComplexTaylorMap(np.tan(self))
  709. def tanh(self) -> "ComplexTaylorMap":
  710. return ComplexTaylorMap(np.tanh(self))
  711. class RealTaylorMap(ComplexTaylorMap):
  712. """Class for Taylor maps of real variable(s).
  713. """
  714. def bounder(self, domains: Iterable[Interval]) -> List[Interval]:
  715. """Method to evaluate the polynomial part of the Taylor map on a Cartesian product of segments via
  716. interval arithmetic.
  717. Args:
  718. domains (Iterable[Interval]): input interval(s) for expansion's variables.
  719. Returns:
  720. List[Interval]: image of inputted interval(s) through Taylor map.
  721. """
  722. if self.is_trivial():
  723. return [Interval.singleton(self._coeff[i, 0]) for i in range(0, self._len)]
  724. # order of at least one
  725. if self.is_univar():
  726. array_intervals = np.array(domains, dtype=Interval)
  727. # Horner's evaluation is not performed on purpose with intervals
  728. output = self.const + self._coeff[:, 1] * array_intervals
  729. for i in range(2, self._order + 1):
  730. output += self._coeff[:, i] * array_intervals**i
  731. else:
  732. # multivariate case
  733. output = np.array([Interval.singleton(0.)] * self._len, dtype=Interval)
  734. powers = []
  735. for domain in domains:
  736. powers_xi = [1., domain]
  737. for j in range(2, self._order + 1):
  738. powers_xi.append(domain ** j)
  739. powers.append(powers_xi)
  740. for exponent, index_coeff in self[0].get_mapping_monom().items():
  741. product = powers[0][exponent[0]]
  742. for index_var, power_var in enumerate(exponent[1:], 1):
  743. product *= powers[index_var][power_var]
  744. output += self._coeff[:, index_coeff] * product
  745. return list(output)
  746. def cbrt(self) -> "RealTaylorMap":
  747. return RealTaylorMap(np.cbrt(self))
  748. def arcsin(self) -> "RealTaylorMap":
  749. return RealTaylorMap(np.arcsin(self))
  750. def arccos(self) -> "RealTaylorMap":
  751. return RealTaylorMap(np.arccos(self))
  752. def arctan(self) -> "RealTaylorMap":
  753. return RealTaylorMap(np.arctan(self))
  754. def arcsinh(self) -> "RealTaylorMap":
  755. return RealTaylorMap(np.arcsinh(self))
  756. def arccosh(self) -> "RealTaylorMap":
  757. return RealTaylorMap(np.arccosh(self))
  758. def arctanh(self) -> "RealTaylorMap":
  759. return RealTaylorMap(np.arctanh(self))