taylor_expans_abstract.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. # taylor_expans_abstract.py: abstract class for Taylor expansions
  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 functools import lru_cache
  15. from abc import ABCMeta, abstractmethod
  16. from typing import List, Union, Iterable, Tuple, Callable, Optional
  17. import numpy as np
  18. from swiftt.algebraic_abstract import AlgebraicAbstract
  19. from swiftt.taylor.tables import algebra_dim
  20. TaylorOrScalar = Union["TaylorExpansAbstract", complex, float]
  21. Scalar1OrND = Union[complex, float, Iterable[complex], Iterable[float]]
  22. default_unknown_name = "x"
  23. default_inverse_name = "y"
  24. landau_symbol = "o"
  25. class TaylorExpansAbstract(AlgebraicAbstract, metaclass=ABCMeta):
  26. """Abstract class for all objects of a Taylor differential algebra.
  27. Attributes:
  28. _n_var (int): number of variable(s) in algebra.
  29. _order (int): order of algebra.
  30. _dim_alg (int): dimension of algebra i.e. number of monomials in the polynomial part of an expansion.
  31. _var_type (type): type of variable(s).
  32. _var_names (List[str]): name of variable(s). They are here only for printing purposes: their consistency is
  33. *not* checked when performing operations on Taylor expansions, for example "x+o(x)"+"y+o(y)"="2x+o(x)".
  34. """
  35. # pre-declared intrinsic functions for scalars
  36. _exp_cst: Callable
  37. _log_cst: Callable
  38. _sqrt_cst: Callable
  39. _cos_cst: Callable
  40. _sin_cst: Callable
  41. _tan_cst: Callable
  42. _cosh_cst: Callable
  43. _sinh_cst: Callable
  44. _tanh_cst: Callable
  45. def __init__(self, n_var: int, order: int, var_type: type, var_names: List[str]) -> None:
  46. """Constructor for TaylorExpansAbstract class.
  47. Args:
  48. n_var (int): number of variable(s) in algebra.
  49. order (int): order of algebra.
  50. var_type (type): type of variable(s).
  51. var_names (List[str]): name of variable(s).
  52. """
  53. self._n_var = n_var
  54. self._order = order
  55. self._var_names = var_names
  56. self._var_type = var_type
  57. self._dim_alg = algebra_dim(n_var, order)
  58. self._coeff: np.ndarray = None
  59. @property
  60. def dim_alg(self) -> int:
  61. """Getter for dimension of algebra.
  62. Returns:
  63. int: dimension of algebra.
  64. """
  65. return self._dim_alg
  66. @property
  67. def order(self) -> int:
  68. """Getter for order of algebra.
  69. Returns:
  70. int: order of algebra.
  71. """
  72. return self._order
  73. @property
  74. def n_var(self) -> int:
  75. """Getter for number of variables in algebra.
  76. Returns:
  77. int: number of variables.
  78. """
  79. return self._n_var
  80. @property
  81. def var_type(self) -> type:
  82. """
  83. Getter for the type of variables.
  84. Returns:
  85. type: type of variables
  86. """
  87. return self._var_type
  88. @property
  89. def var_names(self) -> List[str]:
  90. """Getter for names of variables in algebra. They are implemented in a lazy fashion so if not defined before,
  91. the names are arbitrary.
  92. Returns:
  93. List[str]: names of variables.
  94. """
  95. if self._var_names is None:
  96. # if not provided by user upon initialization, call for default names
  97. self._var_names = TaylorExpansAbstract.get_default_var_names(self._n_var)
  98. if len(self._var_names) != self._n_var:
  99. raise ValueError("The stored names of variables do not match the number of variables.")
  100. if len(set(self._var_names)) != self._n_var:
  101. raise ValueError("At least two variables have exactly the same name.")
  102. return list(self._var_names)
  103. @var_names.setter
  104. def var_names(self, names: List[str]) -> None:
  105. """Setter for names of variables in algebra. It checks the number of variables so is not used in the constructor
  106. in order to preserve computational performance.
  107. Args:
  108. (List[str]): names of variables.
  109. """
  110. if len(names) != self._n_var:
  111. raise ValueError("The number of names does not match the number of variables.")
  112. if len(set(names)) != self._n_var:
  113. raise ValueError("The given names of the variables are not unique.")
  114. self._var_names = list(names)
  115. @property
  116. @abstractmethod
  117. def remainder_term(self) -> str:
  118. """Abstract getter for the remainder (for printing purposes).
  119. Returns:
  120. str: symbolic expression for the remainder.
  121. """
  122. raise NotImplementedError
  123. @property
  124. @abstractmethod
  125. def coeff(self) -> np.ndarray:
  126. """Getter for coefficients of polynomial part.
  127. Returns:
  128. np.ndarray: coefficients of Taylor expansion.
  129. """
  130. raise NotImplementedError
  131. @coeff.setter
  132. @abstractmethod
  133. def coeff(self, coefficients: np.ndarray) -> None:
  134. """Setter for coefficients of polynomial part.
  135. Args:
  136. coefficients (np.ndarray): new coefficients of Taylor expansion.
  137. """
  138. raise NotImplementedError
  139. @property
  140. @abstractmethod
  141. def const(self):
  142. """
  143. Getter for the so-called constant coefficient(s) i.e. associated to the zeroth-order contribution and to be
  144. implemented by all inheritors.
  145. """
  146. raise NotImplementedError
  147. @const.setter
  148. @abstractmethod
  149. def const(self, cst) -> None:
  150. """
  151. Setter for the so-called constant coefficient(s) i.e. associated to the zeroth-order contribution and to be
  152. implemented by all inheritors.
  153. """
  154. raise NotImplementedError
  155. def is_in_same_algebra(self, other: "TaylorExpansAbstract") -> bool:
  156. """Checks if input has same order and number of variables.
  157. Args:
  158. other (TaylorExpansAbstract): Taylor expansion used to compare algebras.
  159. Returns:
  160. bool: True if and only if the two objects have the same order and number of variables.
  161. """
  162. return self._order == other.order and self._n_var == other.n_var
  163. def is_trivial(self) -> bool:
  164. """Function to call to know if expansion is in an algebra of order zero.
  165. Returns:
  166. bool: true if expansion is at order zero, false otherwise.
  167. """
  168. return self._order == 0
  169. def get_const_part(self) -> "TaylorExpansAbstract":
  170. """Abstract method returning the constant part (zeroth-order) and to be implemented by all inheritors.
  171. Returns:
  172. TaylorExpansAbstract: constant part of Taylor expansion.
  173. """
  174. return self.create_const_expansion(self.const)
  175. @abstractmethod
  176. def get_linear_part(self) -> "TaylorExpansAbstract":
  177. """Abstract method returning the linear part and to be implemented by all inheritors.
  178. Returns:
  179. TaylorExpansAbstract: linear part of Taylor expansion.
  180. """
  181. raise NotImplementedError
  182. def get_affine_part(self) -> "TaylorExpansAbstract":
  183. """Method returning the affine part.
  184. Returns:
  185. TaylorExpansAbstract: affine part of Taylor expansion.
  186. """
  187. try:
  188. return self.get_low_order_part(1)
  189. except ValueError:
  190. raise ValueError("There is no affine part in a zeroth-order expansion.")
  191. def get_nonlin_part(self) -> "TaylorExpansAbstract":
  192. """Method returning the non-linear part.
  193. Returns:
  194. TaylorExpansAbstract: non-linear part of Taylor expansion.
  195. """
  196. try:
  197. return self.get_high_order_part(2)
  198. except ValueError:
  199. raise ValueError("There is no non-linear part in an expansion below order two.")
  200. @abstractmethod
  201. def get_nilpo_part(self) -> "TaylorExpansAbstract":
  202. """Abstract method returning the nilpotent part and to be implemented by all inheritors. The nilpotent part (of
  203. the polynomial part in a Taylor expansion) is equal to the original expansion, except for the constant term that
  204. is set to zero.
  205. Returns:
  206. TaylorExpansAbstract: nilpotent part of Taylor expansion i.e. without the zeroth-order contribution.
  207. """
  208. raise NotImplementedError
  209. @abstractmethod
  210. def create_const_expansion(self, const: Scalar1OrND) -> "TaylorExpansAbstract":
  211. raise NotImplementedError
  212. @abstractmethod
  213. def is_nilpotent(self) -> bool:
  214. """Abstract method returning true if the Taylor expansions is nilpotent and to be implemented by all inheritors.
  215. A nilpotent expansion has a vanishing polynomial part if raised to a power greater than the order.
  216. Returns:
  217. bool: true if constant coefficient(s) is (are) zero.
  218. """
  219. raise NotImplementedError
  220. def __pow__(self, alpha: Union[int, float], modulo: Optional[float] = None) -> "TaylorExpansAbstract":
  221. """Method raising Taylor expansion objects to power. For integer, it wraps with the multiplication and squaring.
  222. For non-integer exponent, it is seen as a composition with x -> x ** alpha
  223. Args:
  224. alpha (Union[int, float]): power exponent.
  225. modulo (None): not used for Taylor expansions.
  226. Returns:
  227. TaylorExpansAbstract: Taylor expansion object to input power.
  228. """
  229. if self.is_trivial():
  230. return self.create_const_expansion(self.const ** alpha)
  231. # order of at least one
  232. is_int = isinstance(alpha, int)
  233. if is_int and alpha > 1:
  234. return self._pown(alpha)
  235. if isinstance(alpha, float) or (is_int and alpha < 0):
  236. # compose with x -> x ** alpha
  237. const = self.const
  238. nilpo = self.get_nilpo_part() * (1. / const)
  239. terms = np.ones(self._order + 1)
  240. terms[1:] = np.cumprod([(alpha - i) / (i + 1) for i in range(0, self._order)])
  241. powered = terms[-1] * nilpo
  242. powered.const = terms[-2]
  243. for el in terms[-3::-1]:
  244. powered *= nilpo
  245. powered.const = el
  246. return powered * (const**alpha)
  247. if is_int and alpha == 0:
  248. return self.create_const_expansion(1.)
  249. if is_int and alpha == 1:
  250. return self.copy()
  251. raise ValueError("Wrong exponent")
  252. def reciprocal(self) -> "TaylorExpansAbstract":
  253. """Method defining the reciprocal a.k.a. multiplicative inverse (within the algebra) of a Taylor expansion.
  254. It is computed as the composition from the left by x -> 1 / x
  255. Returns:
  256. TaylorExpansAbstract: multiplicative inverse of Taylor expansion object.
  257. """
  258. if self.is_trivial():
  259. return self.create_const_expansion(1. / self.const)
  260. # order of at least one
  261. const_inv = 1. / self.const
  262. nilpo = self.get_nilpo_part() * (-const_inv)
  263. inverted = nilpo + 1.
  264. for __ in range(1, self._order):
  265. inverted = nilpo * inverted + 1.
  266. return inverted * const_inv
  267. @abstractmethod
  268. def rigorous_integ_once_wrt_var(self, index_var: int) -> "TaylorExpansAbstract":
  269. """Method performing integration with respect to a given unknown variable. The integration constant is zero.
  270. This transformation is rigorous as the order of the expansion is increased by one. In other words, the output
  271. lives in another algebra, of higher dimension.
  272. Args:
  273. index_var (int): variable number w.r.t. which integration needs to be performed.
  274. Returns:
  275. TaylorExpansAbstract: integrated Taylor expansion object w.r.t. input variable number.
  276. """
  277. raise NotImplementedError
  278. def integ_once_wrt_var(self, index_var: int) -> "TaylorExpansAbstract":
  279. """Method performing integration with respect to a given unknown variable while remaining in the same
  280. algebra and to be implemented by all inheritors. This transformation is not rigorous in the sense that the order
  281. of the expansion should be increased by one.
  282. Args:
  283. index_var (int): variable number w.r.t. which integration needs to be performed.
  284. Returns:
  285. TaylorExpansAbstract: integrated Taylor expansion w.r.t. input variable number.
  286. """
  287. return self.rigorous_integ_once_wrt_var(index_var).truncated(self._order)
  288. def integ_wrt_var(self, index_var: int, integ_order: int) -> "TaylorExpansAbstract":
  289. """Method performing integration an arbitrary number of times with respect to a given unknown variable while
  290. remaining in the same algebra. This transformation is not rigorous in the sense that the order of the expansion
  291. should be increased at each integration.
  292. Args:
  293. index_var (int): variable number w.r.t. which integration needs to be performed.
  294. integ_order (int): number of time integration needs to be performed.
  295. Returns:
  296. TaylorExpansAbstract: integrated Taylor expansion w.r.t. input variable number.
  297. """
  298. if integ_order == 1:
  299. return self.integ_once_wrt_var(index_var)
  300. # recursive call with lowered integration order
  301. return self.integ_wrt_var(index_var, integ_order - 1).integ_once_wrt_var(index_var)
  302. @abstractmethod
  303. def deriv_once_wrt_var(self, index_var: int) -> "TaylorExpansAbstract":
  304. """Abstract method performing differentiation with respect to a given unknown variable while remaining in the
  305. same algebra and to be implemented by all inheritors. This transformation is not rigorous in the sense that the
  306. order of the expansion should be decreased by one.
  307. Args:
  308. index_var (int): variable number w.r.t. which differentiation needs to be performed.
  309. Returns:
  310. TaylorExpansAbstract: differentiated Taylor expansion w.r.t. input variable number.
  311. """
  312. raise NotImplementedError
  313. def deriv_wrt_var(self, index_var: int, diff_order: int) -> "TaylorExpansAbstract":
  314. """Method performing differentiation an arbitrary number of times with respect to a given unknown variable while
  315. remaining in the same algebra. This transformation is not rigorous in the sense that the order of the expansion
  316. should be decreased at each integration.
  317. Args:
  318. index_var (int): variable number w.r.t. which differentiation needs to be performed.
  319. diff_order (int): number of time differentiation needs to be performed.
  320. Returns:
  321. TaylorExpansAbstract: differentiated Taylor expansion w.r.t. input variable number.
  322. """
  323. if diff_order == 1:
  324. return self.deriv_once_wrt_var(index_var)
  325. # recursive call with lowered differentiation order
  326. return self.deriv_wrt_var(index_var, diff_order - 1).deriv_once_wrt_var(index_var)
  327. @abstractmethod
  328. def compose(self, other) -> "TaylorExpansAbstract":
  329. """Abstract method to be implemented by all inheritors. Performs composition with inputted Taylor expansion
  330. (must have the same order). The result has the same variables than the input.
  331. Args:
  332. other (TaylorExpansAbstract): argument to be composed on the right-hand side.
  333. Returns:
  334. TaylorExpansAbstract: composed expansion.
  335. """
  336. raise NotImplementedError
  337. @abstractmethod
  338. def pointwise_eval(self, x):
  339. """Abstract method for the evaluation of the Taylor expansion and to be implemented by all inheritors.
  340. Args:
  341. x (Union[complex, float, numpy.ndarray]): point of evaluation.
  342. Returns:
  343. Union[complex, float, numpy.ndarray]: Taylor expansion evaluated at given point.
  344. """
  345. raise NotImplementedError
  346. @abstractmethod
  347. def get_low_order_part(self, order: int) -> "TaylorExpansAbstract":
  348. """Abstract method to be implemented by all inheritors. It removes the high order terms of the expansion whilst
  349. leaving the order unchanged. Hence it is not a rigorous operation.
  350. Args:
  351. order (int): order (included) above which all contributions are removed.
  352. Returns:
  353. TaylorExpansAbstract: low order part of the Taylor expansion.
  354. """
  355. raise NotImplementedError
  356. @abstractmethod
  357. def truncated(self, new_order: int) -> "TaylorExpansAbstract":
  358. """Abstract method for the truncation at a given order and to be implemented by all inheritors. Output lives
  359. in another algebra, of lower dimension.
  360. Args:
  361. new_order (int): order of algebra in which to truncate the Taylor expansion.
  362. Returns:
  363. TaylorExpansAbstract: Taylor expansion truncated at input order.
  364. """
  365. raise NotImplementedError
  366. @abstractmethod
  367. def get_high_order_part(self, order: int) -> "TaylorExpansAbstract":
  368. """Abstract method to be implemented by all inheritors. It removes the low order terms of the expansion whilst
  369. leaving the order unchanged. Hence it is not a rigorous operation.
  370. Args:
  371. order (int): order (included) below which all contributions are removed.
  372. Returns:
  373. TaylorExpansAbstract: high order part of the Taylor expansion.
  374. """
  375. raise NotImplementedError
  376. @abstractmethod
  377. def prolong(self, new_order: int) -> "TaylorExpansAbstract":
  378. """Abstract method for the prolongation (opposite of truncation) at a given order and to be implemented by all
  379. inheritors. Output lives in another algebra, of higher dimension. It is not a rigorous operation as the possible
  380. contributions within the former remainder are ignored.
  381. Args:
  382. new_order (int): order of algebra in which to extend the Taylor expansion.
  383. Returns:
  384. TaylorExpansAbstract: Taylor expansion prolonged at input order.
  385. """
  386. raise NotImplementedError
  387. def prolong_one_order(self) -> "TaylorExpansAbstract":
  388. """Method for the prolongation (opposite of truncation) of exactly one order. Output lives in another algebra,
  389. of higher dimension. It is not a rigorous operation as the possible contributions within the former remainder
  390. are ignored.
  391. Returns:
  392. TaylorExpansAbstract: Taylor expansion prolonged of one order.
  393. """
  394. return self.prolong(self._order + 1)
  395. @abstractmethod
  396. def var_inserted(self, index_new_var: int, unknown_name: Optional[str] = None) -> "TaylorExpansAbstract":
  397. """Abstract method for the addition of a new variable and to be implemented by all inheritors. Output lives in
  398. another algebra, of higher dimension. All its terms associated with the new variable are zero and the other ones
  399. are identical to original expansion.
  400. Args:
  401. index_new_var (int): index of new variable to be added.
  402. unknown_name (str): name of new variable.
  403. Returns:
  404. TaylorExpansAbstract: Taylor expansion with an additional variable.
  405. """
  406. raise NotImplementedError
  407. def var_appended(self, unknown_name: Optional[str] = None) -> "TaylorExpansAbstract":
  408. """Wrapper to add a new variable at the end (stack).
  409. Args:
  410. unknown_name (str): name of new variable.
  411. Returns:
  412. TaylorExpansAbstract: Taylor expansion with an additional variable.
  413. """
  414. return self.var_inserted(self.n_var, unknown_name)
  415. @abstractmethod
  416. def var_removed(self, index_var: int) -> "TaylorExpansAbstract":
  417. """Abstract method for the removal of a variable. Output lives in another algebra, of smaller dimension. All its
  418. terms associated with the old variables only are identical to original expansion.
  419. Args:
  420. index_var (int): index of variable to be removed.
  421. Returns:
  422. TaylorExpansAbstract: Taylor expansion object with a variable removed.
  423. """
  424. raise NotImplementedError
  425. def last_var_removed(self) -> "TaylorExpansAbstract":
  426. """Wrapper to remove the last variable (unstack).
  427. Returns:
  428. TaylorExpansAbstract: Taylor expansion object with the last variable removed.
  429. """
  430. return self.var_removed(self.n_var - 1)
  431. @abstractmethod
  432. def var_eval(self, index_var: int, value: Union[complex, float]) -> "TaylorExpansAbstract":
  433. """Abstract method returning a Taylor expansion object where a variable has been replaced by a fixed scalar
  434. value. In other words, it is a partial evaluation of the polynomial part. It is not rigorous as terms of higher
  435. order hidden in the remainder would need to be considered in this operation.
  436. Args:
  437. index_var (int): index of variable to be evaluated.
  438. value (Union[complex, float]): value to replace given variable.
  439. Returns:
  440. TaylorExpansAbstract: Taylor expansion object with removed dependency.
  441. """
  442. raise NotImplementedError
  443. def last_var_eval(self, value: Union[complex, float]) -> "TaylorExpansAbstract":
  444. """Method returning a Taylor expansion object where the last variable has been replaced by a fixed scalar value.
  445. It wraps the general method for any variable.
  446. Args:
  447. value (Union[complex, float]): value to replace last variable with.
  448. Returns:
  449. TaylorExpansAbstract: Taylor expansion object with removed dependency.
  450. """
  451. return self.var_eval(self._n_var - 1, value)
  452. def contrib_removed(self, indices_var: List[int]) -> "TaylorExpansAbstract":
  453. """Method returning a Taylor expansion object where all coefficients associated to input variables' indices are
  454. set to zero.
  455. Args:
  456. indices_var (List[int]): indices of variables whose contribution is to be removed.
  457. Returns:
  458. TaylorExpansAbstract: Taylor expansion object with removed contributions.
  459. """
  460. output = self.copy()
  461. for index_var in indices_var:
  462. output = output.var_eval(index_var, 0.)
  463. return output
  464. def last_contrib_removed(self) -> "TaylorExpansAbstract":
  465. """Method returning a Taylor expansion object where all coefficients associated to last variable are set to
  466. zero.
  467. Returns:
  468. TaylorExpansAbstract: Taylor expansion object with contributions from last variable removed.
  469. """
  470. return self.contrib_removed([self._n_var - 1])
  471. @abstractmethod
  472. def divided_by_var(self, index_var: int) -> "TaylorExpansAbstract":
  473. """Abstract method returning the Taylor expansion divided by the input variable. This is not a rigorous
  474. operation as the order should be decreased by one.
  475. Args:
  476. index_var (int): index of variable to divide with.
  477. Returns:
  478. TaylorExpansAbstract: Taylor expansion divided by variable.
  479. """
  480. raise NotImplementedError
  481. def linearly_combine_with_another(self, alpha: Union[complex, float], expansion: "TaylorExpansAbstract",
  482. beta: Union[complex, float]) -> "TaylorExpansAbstract":
  483. """Method multiplying with a scalar and then adding with a another expansion also multiplied by some scalar.
  484. Args:
  485. alpha (Union[complex, float]): multiplier for self.
  486. expansion (TaylorExpansAbstract): expansion to be linearly combined with.
  487. beta (Union[complex, float]): multiplier for other expansion.
  488. Returns:
  489. TaylorExpansAbstract: linear combination of self and arguments.
  490. """
  491. return alpha * self + beta * expansion
  492. @staticmethod
  493. def landau_univar(order: int, var_names: List[str]) -> str:
  494. """Static method returning a string representing with a Landau notation the remainder of the expansion in the
  495. univariate case.
  496. Args:
  497. order (int): order of the expansion.
  498. var_names (List[str]): name of the variable.
  499. Returns:
  500. str: negligible part of the Taylor expansion w.r.t. input order.
  501. """
  502. if order == 0:
  503. return landau_symbol + "(1)"
  504. if order == 1:
  505. return landau_symbol + "(" + var_names[0] + ")"
  506. # order of at least two
  507. return landau_symbol + "(" + var_names[0] + "**" + str(order) + ")"
  508. @staticmethod
  509. def landau_multivar(order: int, var_names: List[str]) -> str:
  510. """Static method returning a string representing with a Landau notation the remainder of the expansion in the
  511. multivariate case.
  512. Args:
  513. order (int): order of the expansion.
  514. var_names (List[str]): name of variables.
  515. Returns:
  516. str: negligible part of the Taylor expansion w.r.t. input order.
  517. """
  518. if order == 0:
  519. return landau_symbol + "(1)"
  520. if len(var_names) == 2:
  521. norm = "|(" + var_names[0] + ", " + var_names[1] + ")|"
  522. else:
  523. # at least three variables
  524. norm = "|(" + var_names[0] + ",...," + var_names[-1] + ")|"
  525. if order == 1:
  526. return landau_symbol + "(" + norm + ")"
  527. # order of at least two
  528. return landau_symbol + "(" + norm + "**" + str(order) + ")"
  529. @staticmethod
  530. def get_default_var_names(n_var: int, unknown_name: str = default_unknown_name) -> List[str]:
  531. """Static method returning the default name for the variables of a Taylor expansion.
  532. Args:
  533. n_var (int): number of variables.
  534. unknown_name (str): character referring to variable of expansion.
  535. Returns:
  536. List[str]: name of variables.
  537. """
  538. return [unknown_name] if n_var == 1 else [unknown_name + str(i) for i in range(1, n_var + 1)]
  539. def exp(self) -> "TaylorExpansAbstract":
  540. """Exponential of Taylor expansion object.
  541. Returns:
  542. TaylorExpansAbstract: exponential of Taylor expansion object.
  543. """
  544. if self.is_trivial():
  545. return self.create_const_expansion(self._exp_cst(self.const))
  546. nilpo = self.get_nilpo_part()
  547. order_plus_one = self.order + 1
  548. seq = np.ones(order_plus_one)
  549. seq[1:] /= np.cumprod(np.arange(1, order_plus_one))
  550. expon = nilpo * seq[-1]
  551. expon.const = seq[-2]
  552. for el in seq[-3::-1]:
  553. expon *= nilpo
  554. expon.const = el
  555. return expon * self._exp_cst(self.const)
  556. def log(self) -> "TaylorExpansAbstract":
  557. """Natural logarithm of Taylor expansion object.
  558. Returns:
  559. TaylorExpansAbstract: natural logarithm of Taylor expansion object.
  560. """
  561. if self.is_trivial():
  562. return self.create_const_expansion(self._log_cst(self.const))
  563. order = self.order
  564. const = self.const
  565. nilpo = self.get_nilpo_part() * (1. / const)
  566. seq = np.append([0.], 1. / np.arange(1., order + 1))
  567. seq[2:] *= np.cumprod(-np.ones(order - 1))
  568. logar = nilpo * seq[-1]
  569. for el in seq[-2:0:-1]:
  570. logar.const = el
  571. logar *= nilpo
  572. logar.const = self._log_cst(const)
  573. return logar
  574. def sqrt(self) -> "TaylorExpansAbstract":
  575. """Square root of Taylor expansion.
  576. Returns:
  577. TaylorExpansAbstract: square root of Taylor expansion.
  578. """
  579. if self.is_trivial():
  580. return self.create_const_expansion(self._sqrt_cst(self.const))
  581. const = self.const
  582. nilpo = self.get_nilpo_part() * (1. / const)
  583. order = self.order
  584. terms = np.append([1.], (1. / 2. + np.arange(0., -order, -1.)) / np.arange(1., order + 1))
  585. terms = np.cumprod(terms)
  586. powered = nilpo * terms[-1]
  587. powered.const = terms[-2]
  588. for el in terms[-3::-1]:
  589. powered *= nilpo
  590. powered.const = el
  591. return powered * self._sqrt_cst(const)
  592. @staticmethod
  593. def seq_c_s_zero(order: int, eps: float) -> Tuple[np.ndarray, np.ndarray]:
  594. order_plus_one = order + 1
  595. c_0, s_0 = np.zeros(order_plus_one), np.zeros(order_plus_one)
  596. c_0[0] = s_0[1] = 1.
  597. integers = np.arange(1, order_plus_one)
  598. factors = eps / (integers[:-1] * integers[1:])
  599. for i in range(1, order - 1, 2):
  600. s_0[i + 2] = s_0[i] * factors[i]
  601. for i in range(0, order - 1, 2):
  602. c_0[i + 2] = c_0[i] * factors[i]
  603. return c_0, s_0
  604. @staticmethod
  605. @lru_cache(maxsize=1)
  606. def seq_cos_sin_zero(order: int) -> Tuple[np.ndarray, np.ndarray]:
  607. """Computes the truncated Maclaurin series of the trigonometric cosine and sine functions.
  608. Args:
  609. order (int): order of truncation.
  610. Returns:
  611. np.ndarray: truncated Maclaurin series of cosine.
  612. np.ndarray: truncated Maclaurin series of sine.
  613. """
  614. return TaylorExpansAbstract.seq_c_s_zero(order, -1.)
  615. @staticmethod
  616. @lru_cache(maxsize=1)
  617. def seq_cosh_sinh_zero(order: int) -> Tuple[np.ndarray, np.ndarray]:
  618. """Computes the truncated Maclaurin series of the hyperbolic cosine and sine functions.
  619. Args:
  620. order (int): order of truncation.
  621. Returns:
  622. numpy.ndarray: truncated Maclaurin series of hyperbolic cosine.
  623. numpy.ndarray: truncated Maclaurin series of hyperbolic sine.
  624. """
  625. return TaylorExpansAbstract.seq_c_s_zero(order, 1.)
  626. @staticmethod
  627. def sinusoids(p: "TaylorExpansAbstract", eps: float) -> Tuple["TaylorExpansAbstract", "TaylorExpansAbstract"]:
  628. order = p.order
  629. nilpo = p.get_nilpo_part()
  630. seq_cos, seq_sin = TaylorExpansAbstract.seq_c_s_zero(order, eps)
  631. cosinus = nilpo * seq_cos[-1]
  632. cosinus.const = seq_cos[-2]
  633. sinus = nilpo * seq_sin[-1]
  634. sinus.const = seq_sin[-2]
  635. for el1, el2 in zip(seq_cos[-3::-1], seq_sin[-3::-1]):
  636. cosinus *= nilpo
  637. cosinus.const = el1
  638. sinus *= nilpo
  639. sinus.const = el2
  640. return cosinus, sinus
  641. @staticmethod
  642. def _c_s(p: "TaylorExpansAbstract", eps: float,
  643. a: Union[complex, float], b: Union[complex, float]) -> "TaylorExpansAbstract":
  644. cosine_sine = TaylorExpansAbstract.sinusoids(p, eps)
  645. cosine, sine = cosine_sine[0], cosine_sine[1]
  646. return a * cosine + b * sine
  647. def cos(self) -> "TaylorExpansAbstract":
  648. """Cosine of Taylor expansion object.
  649. Returns:
  650. TaylorExpansAbstract: cosine of Taylor expansion object.
  651. """
  652. const = self.const
  653. c, s = self._cos_cst(const), self._sin_cst(const)
  654. return self.create_const_expansion(c) if self.is_trivial() else self._c_s(self, -1., c, -s)
  655. def sin(self) -> "TaylorExpansAbstract":
  656. """Sine of Taylor expansion object.
  657. Returns:
  658. TaylorExpansAbstract: sine of Taylor expansion object.
  659. """
  660. const = self.const
  661. c, s = self._cos_cst(const), self._sin_cst(const)
  662. return self.create_const_expansion(s) if self.is_trivial() else self._c_s(self, -1., s, c)
  663. def cosh(self) -> "TaylorExpansAbstract":
  664. """Hyperbolic cosine of Taylor expansion object.
  665. Returns:
  666. TaylorExpansAbstract: hyperbolic cosine of Taylor expansion object.
  667. """
  668. const = self.const
  669. ch, sh = self._cosh_cst(const), self._sinh_cst(const)
  670. return self.create_const_expansion(ch) if self.is_trivial() else self._c_s(self, 1., ch, sh)
  671. def sinh(self) -> "TaylorExpansAbstract":
  672. """Hyperbolic sine of Taylor expansion object.
  673. Returns:
  674. TaylorExpansAbstract: hyperbolic sine of Taylor expansion object.
  675. """
  676. const = self.const
  677. ch, sh = self._cosh_cst(const), self._sinh_cst(const)
  678. return self.create_const_expansion(ch) if self.is_trivial() else self._c_s(self, 1., sh, ch)
  679. @staticmethod
  680. def seq_tan_tanh(c: float, eps: float, order: int, tan_cst: Callable, tanh_cst: Callable) -> np.ndarray:
  681. order_plus_one = order + 1
  682. seq = np.empty(order_plus_one)
  683. seq[0] = tanh_cst(c) if eps == -1. else tan_cst(c)
  684. seq[1] = 1. + eps * seq[0] * seq[0]
  685. for i in range(2, order_plus_one):
  686. summed = seq[:i].dot(seq[i - 1::-1])
  687. seq[i] = summed * eps / i
  688. return seq
  689. def tan(self) -> "TaylorExpansAbstract":
  690. """Tangent of Taylor expansion object.
  691. Returns:
  692. TaylorExpansAbstract: tangent of Taylor expansion object.
  693. """
  694. if self.is_trivial():
  695. return self.create_const_expansion(self._tan_cst(self.const))
  696. nilpo = self.get_nilpo_part()
  697. seq = TaylorExpansAbstract.seq_tan_tanh(self.const, 1., self.order,
  698. self._tan_cst, self._tanh_cst)
  699. tangent = nilpo * seq[-1]
  700. tangent.const = seq[-2]
  701. for el in seq[-3::-1]:
  702. tangent *= nilpo
  703. tangent.const = el
  704. return tangent
  705. def tanh(self) -> "TaylorExpansAbstract":
  706. """Hyperbolic tangent of Taylor expansion object.
  707. Returns:
  708. TaylorExpansAbstract: hyperbolic tangent of Taylor expansion object.
  709. """
  710. if self.is_trivial():
  711. return self.create_const_expansion(self._tanh_cst(self.const))
  712. nilpo = self.get_nilpo_part()
  713. seq = self.seq_tan_tanh(self.const, -1., self.order, self._tan_cst, self._tanh_cst)
  714. tangenth = nilpo * seq[-1]
  715. tangenth.const = seq[-2]
  716. for el in seq[-3::-1]:
  717. tangenth *= nilpo
  718. tangenth.const = el
  719. return tangenth