complex_multivar_taylor.py 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244
  1. # complex_multivar_taylor.py: class implementing Taylor expansions of multiple complex 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 Union, List, Dict, Tuple, Iterable, Optional
  15. import cmath
  16. import numpy as np
  17. from numba import njit
  18. from swiftt.taylor.taylor_expans_abstract import TaylorExpansAbstract, landau_symbol, default_unknown_name
  19. from swiftt.taylor.tables import algebra_dim, mapping_monom, flat_mul_table, mul_indices, square_indices, deriv_table
  20. from swiftt.map_abstract import MapAbstract
  21. from swiftt.taylor.tables import factorials
  22. Scalar = Union[complex, float]
  23. class ComplexMultivarTaylor(TaylorExpansAbstract):
  24. """Class for complex Taylor expansions i.e. with a real and an imaginary part for their coefficients and variables.
  25. """
  26. # intrinsic functions for complex
  27. _sqrt_cst = cmath.sqrt
  28. _log_cst, _exp_cst = cmath.log, cmath.exp
  29. _cos_cst, _sin_cst = cmath.cos, cmath.sin
  30. _cosh_cst, _sinh_cst = cmath.cosh, cmath.sinh
  31. _tan_cst, _tanh_cst = cmath.tan, cmath.tanh
  32. def __init__(self, n_var: int, order: int, var_names: List[str]) -> None:
  33. """Constructor for Taylor expansions of multiple complex variables. Result has no assigned coefficients.
  34. Args:
  35. n_var (int): number of variables in algebra.
  36. order (int): order of algebra.
  37. var_names (List[str]): name of variables in algebra.
  38. """
  39. TaylorExpansAbstract.__init__(self, n_var, order, np.complex128, var_names)
  40. @property
  41. def half_order(self) -> int:
  42. """Getter for the half-order of the algebra.
  43. Returns:
  44. int: half-order.
  45. """
  46. return self._order // 2
  47. @TaylorExpansAbstract.order.setter
  48. def order(self, new_order: int) -> None:
  49. if new_order < 0:
  50. raise ValueError("Order must be positive.")
  51. if new_order < self._order:
  52. truncated = self.truncated(new_order)
  53. self._order = new_order
  54. self._dim_alg = algebra_dim(self._n_var, self._order)
  55. self._coeff = truncated._coeff
  56. elif new_order > self._order:
  57. prolonged = self.prolong(new_order)
  58. self._order = new_order
  59. self._dim_alg = algebra_dim(self._n_var, self._order)
  60. self._coeff = prolonged._coeff
  61. @TaylorExpansAbstract.n_var.setter
  62. def n_var(self, n_var: int) -> None:
  63. if n_var == self._n_var:
  64. pass
  65. elif n_var <= 0:
  66. raise ValueError("Number of variables cannot be negative.")
  67. elif n_var == 1 or self._n_var == 1:
  68. raise ValueError("The setter for the number of variables should not be used to create a univariate "
  69. "expansion. Create a instance of the appropriate class instead.")
  70. elif n_var < self._n_var:
  71. inter = self.copy()
  72. for __ in range(0, self._n_var - n_var):
  73. inter = inter.last_var_removed()
  74. self._n_var = n_var
  75. self._dim_alg = algebra_dim(self._n_var, self._order)
  76. self._coeff = inter.coeff
  77. self._var_names = inter.var_names
  78. else:
  79. # variables need to be added
  80. inter = self.copy()
  81. for __ in range(0, n_var - self._n_var):
  82. inter = inter.var_appended()
  83. self._n_var = n_var
  84. self._dim_alg = algebra_dim(self._n_var, self._order)
  85. self._coeff = inter.coeff
  86. self._var_names = inter.var_names
  87. def get_mapping_monom(self) -> Dict[Tuple[int, ...], int]:
  88. """Method returning the mapping between coefficients' numbers and monomials.
  89. Returns:
  90. Dict[Tuple[int, ...], int]: algebra's mapping.
  91. """
  92. return mapping_monom(self._n_var, self._order)
  93. def get_flat_table_mul(self) -> np.ndarray:
  94. """Method returning the flattened algebra's multiplication table.
  95. Returns:
  96. numpy.ndarray: flattened multiplication table.
  97. """
  98. return flat_mul_table(self._n_var, self._order)
  99. def get_indices_mul(self) -> np.ndarray:
  100. """Method returning the algebra's multiplication indices.
  101. Returns:
  102. numpy.ndarray: multiplication indices related to the multiplication table.
  103. """
  104. return mul_indices(self._n_var, self._order)
  105. def get_table_deriv(self) -> np.ndarray:
  106. """Method returning the algebra's differentiation table.
  107. Returns:
  108. numpy.ndarray: differentiation table.
  109. """
  110. return deriv_table(self._n_var, self._order)
  111. def get_square_indices(self) -> np.ndarray:
  112. """Method returning the coefficients' indices corresponding to monomials that are the square of an other
  113. monomial within the algebra.
  114. Returns:
  115. List[int]: so-called square indices.
  116. """
  117. return square_indices(self._n_var, self._order)
  118. def copy(self) -> "ComplexMultivarTaylor":
  119. """Method returning a copy of the Taylor expansion.
  120. Returns:
  121. ComplexMultivarTaylor: copy of the expansion.
  122. """
  123. return self.create_expansion_with_coeff(self._coeff)
  124. def __complex__(self) -> complex:
  125. """Method to cast Taylor expansion as a scalar (according to its constant part).
  126. Returns:
  127. complex: constant part of expansion seen as a scalar.
  128. """
  129. return complex(self.const)
  130. def get_var(self, var_index: int) -> "ComplexMultivarTaylor":
  131. """Method returning an expansion corresponding to the linear monomial for inputted variable index.
  132. Args:
  133. var_index (int): index of variable.
  134. Returns:
  135. ComplexMultivarTaylor: Taylor expansion whose coefficients are all zero except the one associated
  136. to linear term in input variable, which is one.
  137. """
  138. if var_index not in range(0, self.n_var):
  139. raise IndexError("Input variable index does not match any of expansion's")
  140. coeff = np.zeros(self.dim_alg)
  141. coeff[var_index + 1] = 1.
  142. return self.create_expansion_with_coeff(coeff)
  143. def create_expansion_with_coeff(self, coeff) -> "ComplexMultivarTaylor":
  144. """Method to create a new Taylor expansion (in same algebra than self) from given coefficients.
  145. Args:
  146. coeff (Iterable[complex]): coefficients of new expansion.
  147. Returns:
  148. ComplexMultivarTaylor: Taylor expansion corresponding to inputted coefficients.
  149. """
  150. expansion = self.__class__(self._n_var, self._order, self._var_names)
  151. expansion.coeff = coeff
  152. return expansion
  153. def create_const_expansion(self, const: Scalar) -> "ComplexMultivarTaylor":
  154. """Method to create a new Taylor expansion (in same algebra than self) from given constant coefficient.
  155. Args:
  156. const (Union[complex, float]): constant coefficient of new expansion.
  157. Returns:
  158. ComplexMultivarTaylor: Taylor expansion corresponding to inputted constant coefficient.
  159. """
  160. const_expans = self.create_null_expansion()
  161. const_expans.const = const
  162. return const_expans
  163. def create_null_expansion(self) -> "ComplexMultivarTaylor":
  164. """Method to create a new Taylor expansion (in same algebra than self) with null polynomial part.
  165. Returns:
  166. ComplexMultivarTaylor: Taylor expansion with same order and number of variables but null
  167. polynomial part.
  168. """
  169. return self.create_expansion_with_coeff(np.zeros(self._dim_alg, dtype=self._var_type))
  170. def create_monomial_expansion_from_exponent(self, indices: Iterable[int],
  171. factor: Scalar = 1.) -> "ComplexMultivarTaylor":
  172. """Method to create a new Taylor expansion (in same algebra than self) proportional to a given monomial.
  173. Args:
  174. indices (Iterable[int]): monomial's order for each variable.
  175. factor (Optional[Union[complex, float]]): scalar multiplying the monomial.
  176. Returns:
  177. ComplexMultivarTaylor: Taylor expansion proportional to inputted monomial.
  178. """
  179. coeff = np.zeros(self._dim_alg, dtype=self._var_type)
  180. try:
  181. coeff[self.get_mapping_monom()[tuple(indices)]] = factor
  182. except KeyError:
  183. raise ValueError("The monomial is not in the same algebra than this expansion.")
  184. return self.create_expansion_with_coeff(coeff)
  185. def coeff_from_index(self, i: int) -> Scalar:
  186. return self._coeff[i]
  187. @property
  188. def coeff(self) -> np.ndarray:
  189. """Getter for coefficients of polynomial part.
  190. Returns:
  191. Iterable[Union[complex, float]: coefficients of Taylor expansion.
  192. """
  193. return np.array(self._coeff)
  194. @coeff.setter
  195. def coeff(self, coefficients: np.ndarray) -> None:
  196. """Setter for coefficients of polynomial part.
  197. Args:
  198. coefficients (Iterable[Union[complex, float]): new coefficients of Taylor expansion.
  199. """
  200. if len(coefficients) == self._dim_alg:
  201. self._coeff = np.array(coefficients, dtype=self._var_type)
  202. else:
  203. raise ValueError("Input coefficients have wrong number of elements (" + str(coefficients.shape) +
  204. " instead of " + str(self._dim_alg) + ")")
  205. def is_const(self) -> bool:
  206. """Method returning True if the expansion's polynomial part is constant, False otherwise.
  207. Returns:
  208. bool: True if and only if all non-constant coefficients are zero.
  209. """
  210. non_zero = np.array(np.nonzero(self._coeff)[0], dtype=int)
  211. nb_non_zero_coeff = len(non_zero)
  212. if nb_non_zero_coeff == 0:
  213. # the polynomial part is null
  214. return True
  215. if nb_non_zero_coeff != 1:
  216. # expansion has at least two non-zero coefficients
  217. return False
  218. # expansion has one non-zero coefficient, is it the one from the constant part or not
  219. return self._coeff[0] != 0.
  220. def get_linear_part(self) -> "ComplexMultivarTaylor":
  221. """Method returning the linear part of the Taylor expansion.
  222. Returns:
  223. ComplexMultivarTaylor: linear part of Taylor expansion.
  224. """
  225. try:
  226. new_coeff = np.zeros(self._dim_alg, dtype=self._var_type)
  227. new_coeff[1:self._n_var + 1] = np.array(self._coeff[1:self._n_var + 1], dtype=self._var_type)
  228. return self.create_expansion_with_coeff(new_coeff)
  229. except IndexError:
  230. raise ValueError("There is no linear part in a zeroth-order expansion.")
  231. def get_nilpo_part(self) -> "ComplexMultivarTaylor":
  232. """Method returning the nilpotent part of the Taylor expansion.
  233. Returns:
  234. ComplexMultivarTaylor: nilpotent part of Taylor expansion.
  235. """
  236. nilpo = self.copy()
  237. nilpo.const = 0.
  238. return nilpo
  239. def get_high_order_part(self, order: int) -> "ComplexMultivarTaylor":
  240. """Method returning the high order part of the expansion in the same algebra. It is not a rigorous operation.
  241. Args:
  242. order (int): order (included) below which all contributions are removed.
  243. Returns:
  244. ComplexMultivarTaylor: high order part of the Taylor expansion.
  245. """
  246. if 1 <= order <= self._order:
  247. coeff = np.array(self._coeff, dtype=self._var_type)
  248. coeff[:algebra_dim(self._n_var, order - 1)] = 0.
  249. return self.create_expansion_with_coeff(coeff)
  250. raise ValueError("The inputted order exceeds the current order.")
  251. def get_low_order_part(self, order: int) -> "ComplexMultivarTaylor":
  252. """Method returning the low order part of the expansion in the same algebra. It is not a rigorous operation.
  253. Args:
  254. order (int): order (included) above which all contributions are removed.
  255. Returns:
  256. ComplexMultivarTaylor: low order part of the Taylor expansion.
  257. """
  258. if order == 0:
  259. return self.get_const_part()
  260. if order < self._order:
  261. coeff = np.array(self._coeff, dtype=self._var_type)
  262. coeff[algebra_dim(self._n_var, order):] = 0.
  263. return self.create_expansion_with_coeff(coeff)
  264. raise ValueError("The inputted order exceeds the current order.")
  265. def get_low_order_wrt_var(self, index_var: int, order: int) -> "ComplexMultivarTaylor":
  266. """Method returning an expansion where all the terms above a given order for a given variable have been removed.
  267. Args:
  268. index_var (int): index of variable.
  269. order (int): order (included) above which all contributions are removed.
  270. Returns:
  271. ComplexMultivarTaylor: Taylor expansion truncated at inputted order for inputted variable.
  272. """
  273. new_coeff = np.array(self._coeff)
  274. mapping = self.get_mapping_monom()
  275. indices_coeff = [mapping[exponent] for exponent in mapping if exponent[index_var] > order]
  276. new_coeff[indices_coeff] = 0.
  277. return self.create_expansion_with_coeff(new_coeff)
  278. def is_nilpotent(self) -> bool:
  279. """Method returning True if the expansion's polynomial part is nilpotent, False otherwise.
  280. Returns:
  281. bool: True if and only if the constant coefficient is zero.
  282. """
  283. return self.const == 0.
  284. @property
  285. def gradient(self) -> np.ndarray:
  286. """Method returning the evaluation of the first-order derivatives w.r.t. all variables of expansion.
  287. Returns:
  288. numpy.ndarray: first-order derivatives.
  289. """
  290. try:
  291. # the first-order derivatives are exactly the coefficients of the linear part
  292. return np.array(self._coeff[1:self._n_var + 1], dtype=self._var_type)
  293. except IndexError:
  294. raise ValueError("No gradient can be derived from a zeroth-order expansion.")
  295. @property
  296. def hessian(self) -> np.ndarray:
  297. """Method returning the evaluation of the second-order derivatives w.r.t. all variables of expansion.
  298. Returns:
  299. numpy.ndarray: second-order derivatives expressed in Hessian form.
  300. """
  301. derivs = self.get_all_partial_deriv_up_to(2)
  302. mapping = self.get_mapping_monom()
  303. hessian = np.empty((self._n_var, self._n_var), dtype=self._var_type)
  304. exponent = [0] * self._n_var
  305. for i in range(0, self._n_var):
  306. exponent[i] = 2
  307. hessian[i, i] = derivs[mapping[tuple(exponent)]]
  308. exponent[i] = 1
  309. for j in range(0, i):
  310. exponent[j] = 1
  311. hessian[i, j] = hessian[j, i] = derivs[mapping[tuple(exponent)]]
  312. exponent[j] = 0
  313. exponent[i] = 0
  314. return hessian
  315. def get_partial_deriv(self, exponents: Tuple[int, ...]) -> Scalar:
  316. output = self.coeff_from_index(self.get_mapping_monom()[exponents])
  317. return output * np.prod(factorials(self.order)[list(exponents)])
  318. def get_all_partial_deriv_up_to(self, order: int) -> np.ndarray:
  319. """Method returning the partial derivatives of the expansion up to a given order. In other words, it converts
  320. the polynomial coefficients by multiplying them with factorials.
  321. Args:
  322. order (int): order (included) up to which partial derivatives need to be outputted.
  323. Returns:
  324. numpy.ndarray: partial derivatives w.r.t. expansion's variables.
  325. """
  326. if order > self._order or order < 0:
  327. raise ValueError("Order of differentiation must be non-negative and not exceed order of algebra")
  328. output = np.array(self._coeff[:algebra_dim(self._n_var, order)], dtype=self.var_type)
  329. fac = factorials(order)
  330. for exponent, index_coeff in self.get_mapping_monom().items():
  331. output[index_coeff] *= np.prod(fac[list(exponent)])
  332. if index_coeff + 1 == len(output):
  333. # the next index is outside the scope of interest
  334. break
  335. return output
  336. def __mod__(self, other: float) -> "ComplexMultivarTaylor":
  337. """Modulo function when the left-hand side is a Taylor expansion.
  338. Args:
  339. other (float): modulo argument.
  340. Returns:
  341. ComplexMultivarTaylor: Taylor expansion's after modulo operation.
  342. """
  343. modulo = self.copy()
  344. modulo.const = modulo.const % other
  345. return modulo
  346. def __add__(self, other) -> "ComplexMultivarTaylor":
  347. """Method defining right-hand side addition. Works both with scalars and other expansions.
  348. Args:
  349. other (Union[ComplexMultivarTaylor, complex, float]): quantity to be added.
  350. Returns:
  351. ComplexMultivarTaylor: Taylor expansion summed with argument.
  352. """
  353. if isinstance(other, ComplexMultivarTaylor):
  354. if self.is_in_same_algebra(other):
  355. return self.create_expansion_with_coeff(self._coeff + other._coeff)
  356. raise ValueError("Expansions to be summed are not from the same algebra")
  357. # scalar case
  358. added = self.copy()
  359. added._coeff[0] += other
  360. return added
  361. def __sub__(self, other) -> "ComplexMultivarTaylor":
  362. """Method defining right-hand side subtraction. Works both with scalars and other expansions.
  363. Args:
  364. other (Union[ComplexMultivarTaylor, complex, float]): quantity to be subtracted.
  365. Returns:
  366. ComplexMultivarTaylor: Taylor expansion subtracted with argument.
  367. """
  368. if isinstance(other, ComplexMultivarTaylor):
  369. if self.is_in_same_algebra(other):
  370. return self.create_expansion_with_coeff(self._coeff - other._coeff)
  371. raise ValueError("Expansions to be subtracted are not from the same algebra")
  372. # scalar case
  373. subtracted = self.copy()
  374. subtracted._coeff[0] -= other
  375. return subtracted
  376. def __rsub__(self, other) -> "ComplexMultivarTaylor":
  377. """Method defining left-hand side subtraction. Works both with scalars and other expansions.
  378. Args:
  379. other (Union[ComplexMultivarTaylor, complex, float]): quantity to perform subtraction on.
  380. Returns:
  381. ComplexMultivarTaylor: argument subtracted with Taylor expansion.
  382. """
  383. if isinstance(other, ComplexMultivarTaylor):
  384. if self.is_in_same_algebra(other):
  385. return self.create_expansion_with_coeff(other._coeff - self._coeff)
  386. raise ValueError("Expansions to be subtracted are not from the same algebra")
  387. # scalar case
  388. subtracted = -self
  389. subtracted._coeff[0] += other
  390. return subtracted
  391. def __neg__(self) -> "ComplexMultivarTaylor":
  392. """Method defining negation (additive inverse). Overwrites parent implementation for performance.
  393. Returns:
  394. ComplexMultivarTaylor: opposite of object (from the point of view of addition).
  395. """
  396. return self.create_expansion_with_coeff(-self._coeff)
  397. def linearly_combine_with_another(self, alpha: Scalar, expansion: "ComplexMultivarTaylor",
  398. beta: Scalar) -> "ComplexMultivarTaylor":
  399. """Method multiplying with a scalar and then adding with a another expansion also multiplied by some scalar.
  400. Overwritten for speed purposes.
  401. Args:
  402. alpha (Union[complex, float]): multiplier for self.
  403. expansion (ComplexMultivarTaylor): expansion to be linearly combined with.
  404. beta (Union[complex, float]): multiplier for other expansion.
  405. Returns:
  406. ComplexMultivarTaylor: linear combination of self and arguments.
  407. """
  408. try:
  409. return self.create_expansion_with_coeff(alpha * self._coeff + beta * expansion._coeff)
  410. except ValueError as exc:
  411. raise exc
  412. def linearly_combine_with_many(self, alpha: Scalar, expansions: Iterable["ComplexMultivarTaylor"],
  413. betas: Iterable[Scalar]) -> "ComplexMultivarTaylor":
  414. """Method multiplying with a scalar and then adding an arbitrary number of expansions also multiplied by some
  415. scalar.
  416. Args:
  417. alpha (Union[complex, float]): multiplier for self.
  418. expansions (Iterable[ComplexMultivarTaylor]): expansions to be linearly combined with.
  419. betas (Iterable[Union[complex, float]]): multipliers for other expansions.
  420. Returns:
  421. ComplexMultivarTaylor: linear combination of self and arguments.
  422. """
  423. new_coeff = alpha * self._coeff
  424. for beta, expansion in zip(betas, expansions):
  425. new_coeff += beta * expansion._coeff
  426. return self.create_expansion_with_coeff(new_coeff)
  427. @staticmethod
  428. @njit(cache=True)
  429. def mul_multivar(coeff: np.ndarray, other_coeff: np.ndarray, square_ind: np.ndarray,
  430. table_mul: np.ndarray, indices_mul: np.ndarray) -> np.ndarray:
  431. """Static method transforming two series of coefficients into the coefficients of the product of multivariate
  432. Taylor expansions. It emulates the polynomial product and the truncation at the same time. Method is static so
  433. that it can be replaced by faster C code if applicable.
  434. Args:
  435. coeff (numpy.ndarray): first set of coefficients.
  436. other_coeff (numpy.ndarray): second set of coefficients.
  437. square_ind (numpy.ndarray): precomputed indices corresponding to monomials which are the square
  438. of another monomial in the algebra.
  439. table_mul (numpy.ndarray): flattened algebra's multiplication table.
  440. indices_mul (numpy.ndarray): algebra's multiplication indices.
  441. Returns:
  442. numpy.ndarray: coefficient corresponding to product.
  443. """
  444. multiplied_coeff = coeff[0] * other_coeff + other_coeff[0] * coeff
  445. dim_half_order = len(square_ind)
  446. symmetric_terms = coeff[:dim_half_order] * other_coeff[:dim_half_order]
  447. multiplied_coeff[0] = 0.
  448. multiplied_coeff[square_ind] += symmetric_terms
  449. slices = indices_mul[2:] - indices_mul[1:-1]
  450. for i, (slice_index, el1, el2) in enumerate(zip(slices, coeff[2:], other_coeff[2:]), 2):
  451. multiplied_coeff[table_mul[indices_mul[i - 1] + 1:indices_mul[i]]] += el1 * other_coeff[1:slice_index] \
  452. + el2 * coeff[1:slice_index]
  453. return multiplied_coeff
  454. def _mul_expansion(self, other: "ComplexMultivarTaylor") -> np.ndarray:
  455. """Wrapper to static method on coefficients to multiply multivariate expansion.
  456. Args:
  457. other (ComplexMultivarTaylor): Taylor expansion to be multiplied with.
  458. Returns:
  459. numpy.ndarray: coefficients of product of Taylor expansions.
  460. """
  461. return self.mul_multivar(self._coeff, other._coeff, self.get_square_indices(),
  462. self.get_flat_table_mul(), self.get_indices_mul())
  463. def __mul__(self, other) -> "ComplexMultivarTaylor":
  464. """Method defining right-hand side multiplication. It works for the external multiplication i.e. with scalars
  465. and for the internal one, that is between expansions.
  466. Args:
  467. other (Union[ComplexMultivarTaylor, complex, float]): quantity to be multiplied with.
  468. Returns:
  469. ComplexMultivarTaylor: multiplied objects.
  470. """
  471. if isinstance(other, ComplexMultivarTaylor):
  472. try:
  473. multiplied_coeff = self._mul_expansion(other)
  474. return self.create_expansion_with_coeff(multiplied_coeff)
  475. except ValueError:
  476. raise ValueError("Expansions to be multiplied are not from the same algebra")
  477. if isinstance(other, (MapAbstract, np.ndarray)):
  478. return other * self
  479. # scalar case
  480. return self.create_expansion_with_coeff(other * self._coeff)
  481. @staticmethod
  482. @njit(cache=True)
  483. def pow2_multivar(coeff: np.ndarray, square_ind: np.ndarray,
  484. table_mul: np.ndarray, indices_mul: np.ndarray) -> np.ndarray:
  485. """Static method transforming coefficients into the coefficients of the square of a multivariate
  486. Taylor expansion. It emulates the polynomial product and the truncation at the same time. Method is static so
  487. that it can be replaced by faster C code if applicable.
  488. Args:
  489. coeff (numpy.ndarray): first set of coefficients.
  490. square_ind (numpy.ndarray): precomputed indices corresponding to monomials which are the square
  491. of another monomial in the algebra.
  492. table_mul (np.ndarray): flattened algebra's multiplication table.
  493. indices_mul (numpy.ndarray): algebra's multiplication indices.
  494. Returns:
  495. numpy.ndarray: coefficients corresponding to square.
  496. """
  497. twice_coeff = 2. * coeff
  498. pow2_coeff = coeff[0] * twice_coeff
  499. squared_terms = coeff[:len(square_ind)] ** 2
  500. pow2_coeff[0] = 0.
  501. pow2_coeff[square_ind] += squared_terms
  502. slices = indices_mul[2:] - indices_mul[1:-1]
  503. for i, (slice_index, el) in enumerate(zip(slices, coeff[2:]), 2):
  504. pow2_coeff[table_mul[indices_mul[i - 1] + 1:indices_mul[i]]] += twice_coeff[1:slice_index] * el
  505. return pow2_coeff
  506. def pow2(self) -> "ComplexMultivarTaylor":
  507. """Method to raise Taylor expansion to the power 2 i.e. compute its multiplicative square.
  508. Returns:
  509. ComplexMultivarTaylor: Taylor expansion raised to power 2.
  510. """
  511. coeff = self.pow2_multivar(self._coeff, self.get_square_indices(), self.get_flat_table_mul(),
  512. self.get_indices_mul())
  513. return self.create_expansion_with_coeff(coeff)
  514. def rigorous_integ_once_wrt_var(self, index_var: int) -> "ComplexMultivarTaylor":
  515. """Method performing integration with respect to a given unknown variable. The integration constant is zero.
  516. This transformation is rigorous as the order of the expansion is increased by one. In other words, the output
  517. lives in another algebra, of higher dimension.
  518. Args:
  519. index_var (int): variable number w.r.t. which integration needs to be performed.
  520. Returns:
  521. ComplexMultivarTaylor: integrated Taylor expansion w.r.t. input variable number.
  522. """
  523. new_expansion = self.__class__(self._n_var, self._order + 1, self._var_names)
  524. new_coeff = np.zeros(new_expansion.dim_alg, dtype=self._var_type)
  525. inv_exponents_plus_one = 1. / np.arange(1., self._order + 2)
  526. new_mapping = new_expansion.get_mapping_monom()
  527. for exponent, index_coeff in self.get_mapping_monom().items():
  528. new_tuple = exponent[:index_var] + (exponent[index_var] + 1,) + exponent[index_var + 1:]
  529. new_coeff[new_mapping[new_tuple]] = self._coeff[index_coeff] * inv_exponents_plus_one[exponent[index_var]]
  530. new_expansion.coeff = new_coeff
  531. return new_expansion
  532. def integ_once_wrt_var(self, index_var: int) -> "ComplexMultivarTaylor":
  533. """Method performing integration with respect to a given unknown variable while remaining in the same
  534. algebra. The integration constant is zero. This transformation is not rigorous in the sense that the order of
  535. the expansion should be increased by one.
  536. Args:
  537. index_var (int): variable number w.r.t. which integration needs to be performed.
  538. Returns:
  539. ComplexMultivarTaylor: integrated Taylor expansion w.r.t. input variable number.
  540. """
  541. if self.is_trivial():
  542. return self.create_null_expansion()
  543. # order of at least one
  544. coeff = np.zeros(self._dim_alg, dtype=self._var_type)
  545. nb = algebra_dim(self._n_var, self._order - 1)
  546. weights = np.array(list(self.get_mapping_monom().keys()))[:nb, index_var] + 1.
  547. coeff[self.get_table_deriv()[:nb, index_var]] = self._coeff[:nb] / weights
  548. return self.create_expansion_with_coeff(coeff)
  549. def deriv_once_wrt_var(self, index_var: int) -> "ComplexMultivarTaylor":
  550. """Method performing differentiation with respect to a given unknown variable while remaining in the same
  551. algebra. This transformation is not rigorous in the sense that the order of the expansion should be decreased by
  552. one.
  553. Args:
  554. index_var (int): variable number w.r.t. which differentiation needs to be performed.
  555. Returns:
  556. ComplexMultivarTaylor: differentiated Taylor expansion w.r.t. input variable number.
  557. """
  558. if self.is_trivial():
  559. return self.create_null_expansion()
  560. # order of at least one
  561. coeff = np.zeros(self._dim_alg, dtype=self._var_type)
  562. nb = algebra_dim(self._n_var, self._order - 1)
  563. links = self.get_table_deriv()[:nb, index_var]
  564. integers = np.array(list(self.get_mapping_monom().keys()))[links, index_var]
  565. coeff[:nb] = self._coeff[links] * integers
  566. return self.create_expansion_with_coeff(coeff)
  567. def compose(self, other) -> "ComplexMultivarTaylor":
  568. """Method performing composition with inputted Taylor map (must have the same order and as many elements as
  569. self has variables). The result is a single expansion and has the same variables than the input.
  570. Args:
  571. other (ComplexTaylorMap): Taylor map to be composed on the right-hand side.
  572. Returns:
  573. ComplexMultivarTaylor: composed expansion.
  574. """
  575. from swiftt.taylor.taylor_map import ComplexTaylorMap
  576. if not isinstance(other, ComplexTaylorMap):
  577. raise ValueError("Multivariate expansions can only be composed on the right by Taylor maps")
  578. if other.order != self._order:
  579. raise ValueError("Expansions to be composed must have same order")
  580. if not other.is_nilpotent():
  581. raise ValueError("Right-hand-side map for composition must be nilpotent")
  582. if len(other) != self._n_var:
  583. raise ValueError("Right-hand-side map does not have as many elements as left-hand side has"
  584. "variables")
  585. if self.is_trivial():
  586. return other[0].create_const_expansion(self.const)
  587. # order of at least one
  588. mapping = self.get_mapping_monom()
  589. powers = list(mapping.keys())
  590. already_computed = dict(zip(powers[1:self.n_var + 1], other))
  591. # define recursive function computing required products of expansions with memoization
  592. def lazy(power: Tuple[int, ...]) -> ComplexMultivarTaylor:
  593. k = new_tuple = None
  594. for index_var, power_var in enumerate(power):
  595. if power_var > 0:
  596. k = index_var
  597. new_tuple = power[:index_var] + (power_var - 1,) + power[index_var + 1:]
  598. try:
  599. already_computed[power] = already_computed[new_tuple] * other[index_var]
  600. # this product could be obtained with an already computed quantity, so go to return
  601. break
  602. except KeyError:
  603. # keep trying with other variables
  604. pass
  605. else: # no break
  606. already_computed[power] = lazy(new_tuple) * other[k] # recursive call
  607. return already_computed[power]
  608. rhs_coeff = np.zeros((self._dim_alg, other[0].dim_alg), dtype=self._var_type)
  609. rhs_coeff[0, 0] = 1.
  610. rhs_coeff[1:self.n_var + 1, :] = other.coeff
  611. # this implementation is optimized for a sparse expansion (many vanishing coeff) on the left-hand side
  612. for j in (np.nonzero(self._coeff[self._n_var + 1:])[0] + self._n_var + 1):
  613. rhs_coeff[j, :] = lazy(powers[j]).coeff
  614. return other[0].create_expansion_with_coeff(self._coeff.dot(rhs_coeff))
  615. def pointwise_eval(self, x: np.ndarray) -> np.ndarray:
  616. """Method for the evaluation of the Taylor expansion on a given point.
  617. Args:
  618. x (numpy.ndarray): point of evaluation.
  619. Returns:
  620. numpy.ndarray: Taylor expansion evaluated at given point.
  621. """
  622. if self.is_trivial():
  623. return self.const
  624. if len(x) == self._n_var:
  625. mapping = self.get_mapping_monom()
  626. products = np.ones(self._dim_alg, dtype=self._var_type)
  627. for exponent, index_coeff in mapping.items():
  628. for index_var, power_var in enumerate(exponent):
  629. if power_var > 0:
  630. new_tuple = exponent[:index_var] + (power_var - 1,) + exponent[index_var + 1:]
  631. products[index_coeff] = x[index_var] * products[mapping[new_tuple]]
  632. break
  633. return self._coeff.dot(products)
  634. raise ValueError("Inconsistent point for evaluation")
  635. def massive_eval(self, Xs: np.ndarray) -> np.ndarray:
  636. """Method for the evaluation of the Taylor expansion on a range of points (vectorized evaluation).
  637. Args:
  638. Xs (numpy.ndarray): points of evaluation.
  639. Returns:
  640. numpy.ndarray: Taylor expansion evaluated at given points.
  641. """
  642. if Xs.shape[1] != self._n_var:
  643. raise IndexError("The number of columns of the input should equal the number of variables in the "
  644. "expansion.")
  645. mapping = self.get_mapping_monom()
  646. products = np.ones((self._dim_alg, Xs.shape[0]), dtype=self._var_type)
  647. for exponent, index_coeff in mapping.items():
  648. for index_var, power_var in enumerate(exponent):
  649. if power_var > 0:
  650. new_tuple = exponent[:index_var] + (power_var - 1,) + exponent[index_var + 1:]
  651. products[index_coeff, :] = Xs[:, index_var] * products[mapping[new_tuple], :]
  652. return self._coeff.dot(products)
  653. def __call__(self, *args, **kwargs):
  654. """Method for calling the Taylor expansion. Wraps several possibilities: evaluation and composition with a map.
  655. Returns:
  656. Union[ComplexMultivarTaylor, numpy.ndarray]: Taylor expansion called on input.
  657. """
  658. return self.compose(args[0]) if isinstance(args[0][0], ComplexMultivarTaylor) else self.pointwise_eval(args[0])
  659. @property
  660. def remainder_term(self) -> str:
  661. return TaylorExpansAbstract.landau_multivar(self._order, self._var_names)
  662. @property
  663. def const(self) -> Union[float, complex]:
  664. """
  665. Getter for the so-called constant coefficient i.e. associated to the zeroth-order contribution.
  666. Returns:
  667. Union[float, complex]: constant.
  668. """
  669. return self._coeff[0]
  670. @const.setter
  671. def const(self, cst: Union[float, complex]) -> None:
  672. """
  673. Setter for the so-called constant coefficient i.e. associated to the zeroth-order contribution.
  674. Args:
  675. cst (Union[float, complex]): new constant.
  676. """
  677. self._coeff[0] = cst
  678. def __str__(self) -> str:
  679. """Method to cast complex Taylor expansion as a string.
  680. Returns:
  681. str: string representing the Taylor expansion.
  682. """
  683. string = str(self.const)
  684. var_names = self.var_names
  685. if not self.is_trivial():
  686. for exponent, index_coeff in self.get_mapping_monom().items():
  687. if self._coeff[index_coeff] != 0. and index_coeff != 0:
  688. string += " + " + str(self._coeff[index_coeff])
  689. for index_var, power_var in enumerate(exponent):
  690. if power_var != 0:
  691. string += " * " + var_names[index_var]
  692. if power_var > 1:
  693. string += "**" + str(power_var)
  694. return string + " + " + self.remainder_term
  695. def divided_by_var(self, index_var: int) -> "ComplexMultivarTaylor":
  696. """Method returning the Taylor expansion divided by the input variable. This is not a rigorous operation as the
  697. order should be decreased by one.
  698. Args:
  699. index_var (str): index of variable to divide with.
  700. Returns:
  701. ComplexMultivarTaylor: Taylor expansion divided by variable.
  702. """
  703. if self.const != 0.:
  704. raise ValueError("Cannot divide by variable if constant term is not zero.")
  705. coeff = np.zeros(self._dim_alg, dtype=self._var_type)
  706. mapping = self.get_mapping_monom()
  707. for exponent, index_coeff in mapping.items():
  708. if self._coeff[index_coeff] != 0.:
  709. if exponent[index_var] == 0:
  710. raise ValueError("Cannot divide by variable if there are non-zero terms without this variable.")
  711. new_exponent = np.array(exponent, dtype=int)
  712. new_exponent[index_var] -= 1
  713. coeff[mapping[tuple(new_exponent)]] = self._coeff[index_coeff]
  714. return self.create_expansion_with_coeff(coeff)
  715. def contrib_removed(self, indices_var: List[int]) -> "ComplexMultivarTaylor":
  716. """Method returning a Taylor expansion where all coefficients associated to input variables' indices are set to
  717. zero.
  718. Args:
  719. indices_var (List[int]): indices of variables whose contribution is to be removed.
  720. Returns:
  721. ComplexMultivarTaylor: Taylor expansion with removed contributions.
  722. """
  723. coeff = np.array(self._coeff)
  724. try:
  725. exponents = np.array(list(self.get_mapping_monom().keys()))[:, indices_var]
  726. except TypeError:
  727. raise ValueError("At least one inputted variable index is not an integer")
  728. except IndexError:
  729. raise ValueError("At least one inputted variable index does not exist in this algebra")
  730. for i in range(0, len(indices_var)):
  731. coeff[exponents[:, i] != 0] = 0.
  732. return self.create_expansion_with_coeff(coeff)
  733. def var_eval(self, index_var: int, value: Scalar) -> "ComplexMultivarTaylor":
  734. """Method returning a Taylor expansion where a variable has been replaced by a fixed scalar value. In other
  735. words, it is a partial evaluation of the polynomial part. It is not rigorous as terms of higher order hidden in
  736. the remainder would need to be considered in this operation.
  737. Args:
  738. index_var (int): index of variable to be evaluated.
  739. value (Union[complex, float]): value to replace given variable.
  740. Returns:
  741. ComplexMultivarTaylor: Taylor expansion with removed dependency.
  742. """
  743. if index_var >= self._n_var or index_var < 0:
  744. raise IndexError("The inputted index does not correspond to any variable of the expansion.")
  745. powers = np.cumprod(np.full(self._order, value, dtype=self._var_type))
  746. new_coeff = np.zeros(self._dim_alg, dtype=self._var_type)
  747. mapping = self.get_mapping_monom()
  748. tuple_0 = (0,)
  749. for exponent, index_coeff in mapping.items():
  750. if exponent[index_var] == 0:
  751. new_coeff[index_coeff] += self._coeff[index_coeff]
  752. else:
  753. new_exponent = exponent[:index_var] + tuple_0 + exponent[index_var + 1:]
  754. new_coeff[mapping[new_exponent]] += self._coeff[index_coeff] * powers[exponent[index_var] - 1]
  755. return self.create_expansion_with_coeff(new_coeff)
  756. def truncated(self, new_order: int) -> "ComplexMultivarTaylor":
  757. """Method for the truncation at a given order. Output lives in another algebra, of lower dimension.
  758. Args:
  759. new_order (int): order of algebra in which to truncate the Taylor expansion.
  760. Returns:
  761. ComplexMultivarTaylor: Taylor expansion truncated at input order.
  762. """
  763. if new_order < 0 or new_order >= self._order or self.is_trivial():
  764. raise ValueError("Input order must be an integer between zero and order of initial algebra")
  765. truncated = self.__class__(self._n_var, new_order, self._var_names)
  766. truncated.coeff = self._coeff[:truncated.dim_alg]
  767. return truncated
  768. def prolong(self, new_order: int) -> "ComplexMultivarTaylor":
  769. """Method for the prolongation (opposite of truncation) at a given order. Output lives in another algebra, of
  770. higher dimension. It is not a rigorous operation as the possible contributions within the former remainder are
  771. ignored.
  772. Args:
  773. new_order (int): order of algebra in which to prolong the Taylor expansion.
  774. Returns:
  775. ComplexMultivarTaylor: Taylor expansion prolonged at input order.
  776. """
  777. if new_order <= self._order:
  778. raise ValueError("Input order must be an integer greater than order of initial algebra")
  779. prolonged = self.__class__(self._n_var, new_order, self._var_names)
  780. new_coeff = np.zeros(prolonged.dim_alg, dtype=self._var_type)
  781. new_coeff[:self._dim_alg] = np.array(self._coeff, dtype=self._var_type)
  782. prolonged.coeff = new_coeff
  783. return prolonged
  784. def prolong_one_order(self) -> "ComplexMultivarTaylor":
  785. """Method for the prolongation (opposite of truncation) of one order. Output lives in another algebra, of
  786. higher dimension. It is not a rigorous operation as the possible contributions within the former remainder are
  787. ignored.
  788. Returns:
  789. ComplexMultivarTaylor: Taylor expansion prolonged by one order.
  790. """
  791. prolonged = self.__class__(self._n_var, self._order + 1, self._var_names)
  792. new_coeff = np.zeros(prolonged.dim_alg, dtype=self._var_type)
  793. new_coeff[:self._dim_alg] = np.array(self._coeff, dtype=self._var_type)
  794. prolonged.coeff = new_coeff
  795. return prolonged
  796. def var_inserted(self, index_new_var: int, unknown_name: Optional[str] = None) -> "ComplexMultivarTaylor":
  797. """Method for the addition of a new variable. Output lives in another algebra, of higher dimension. All its
  798. terms associated with the new variable are zero and the other ones are identical to original expansion.
  799. Args:
  800. index_new_var (int): index of new variable to be added.
  801. unknown_name (str): name of new variable.
  802. Returns:
  803. ComplexMultivarTaylor: Taylor expansion with an additional variable.
  804. """
  805. if unknown_name is None:
  806. unknown_name = default_unknown_name + str(self._n_var + 1)
  807. if unknown_name in self.var_names:
  808. raise ValueError("Proposed name of new variable already exists.")
  809. new_var_names = list(self._var_names)
  810. new_var_names.append(unknown_name)
  811. new_expansion = self.__class__(self._n_var + 1, self._order, new_var_names)
  812. new_coeff = np.zeros(new_expansion.dim_alg, dtype=self._var_type)
  813. if not self.is_trivial():
  814. for new_exponent, new_index_coeff in new_expansion.get_mapping_monom().items():
  815. if new_exponent[index_new_var] == 0:
  816. new_tuple = new_exponent[:index_new_var] + new_exponent[index_new_var + 1:]
  817. new_coeff[new_index_coeff] = self._coeff[self.get_mapping_monom()[new_tuple]]
  818. else:
  819. new_coeff[0] = self.const
  820. new_expansion.coeff = new_coeff
  821. return new_expansion
  822. def var_removed(self, index_var: int) -> "ComplexMultivarTaylor":
  823. """Method for the removal of a variable. Output lives in another algebra, of smaller dimension. All its
  824. terms associated with the old variables only are identical to original expansion.
  825. Args:
  826. index_var (int): index of variable to be removed.
  827. Returns:
  828. ComplexMultivarTaylor: Taylor expansion with a variable removed.
  829. """
  830. if self._n_var == 1:
  831. raise ValueError("A univariate expansion cannot have variables removed.")
  832. if index_var >= self._n_var or index_var < 0:
  833. raise IndexError("The inputted index does not correspond to any variable of the expansion.")
  834. new_var_names = list(self.var_names)
  835. del new_var_names[index_var]
  836. if self._n_var == 2:
  837. # TODO: improve the treatment of this case
  838. if self._var_type == np.complex128:
  839. from swiftt.taylor.complex_univar_taylor import ComplexUnivarTaylor
  840. new_expansion = ComplexUnivarTaylor(self._order, new_var_names)
  841. else:
  842. from swiftt.taylor.real_univar_taylor import RealUnivarTaylor
  843. new_expansion = RealUnivarTaylor(self._order, new_var_names)
  844. coeff = np.zeros(new_expansion.dim_alg, dtype=self._var_type)
  845. if not self.is_trivial():
  846. for old_exponent, index_old_coeff in self.get_mapping_monom().items():
  847. if old_exponent[index_var] == 0:
  848. coeff[sum(old_exponent)] = self._coeff[index_old_coeff]
  849. else:
  850. coeff[0] = self.const
  851. else:
  852. new_expansion = self.__class__(self._n_var - 1, self._order, new_var_names)
  853. coeff = np.zeros(new_expansion.dim_alg, dtype=self._var_type)
  854. if not self.is_trivial():
  855. new_mapping = new_expansion.get_mapping_monom()
  856. for old_exponent, index_old_coeff in self.get_mapping_monom().items():
  857. if old_exponent[index_var] == 0:
  858. new_tuple = old_exponent[:index_var] + old_exponent[index_var + 1:]
  859. coeff[new_mapping[new_tuple]] = self._coeff[index_old_coeff]
  860. else:
  861. coeff[0] = self.const
  862. new_expansion.coeff = coeff
  863. return new_expansion
  864. def create_expansion_from_smaller_algebra(self, expansion: "ComplexMultivarTaylor") -> "ComplexMultivarTaylor":
  865. """Method to create a Taylor expansion in same algebra than self, from an expansion in another algebra with
  866. same order but less variables. Names of intersecting variables must be identical otherwise the function does not
  867. work.
  868. Args:
  869. expansion (ComplexMultivarTaylor): Taylor expansion to extend in current algebra.
  870. Returns:
  871. ComplexMultivarTaylor: Taylor expansion whose polynomial coefficients are all zero except the
  872. ones related only to the variables of the input that are then identical to them.
  873. """
  874. if expansion.order != self._order:
  875. raise ValueError("The inputted expansion has a different order.")
  876. if expansion.n_var >= self._n_var:
  877. raise ValueError("The inputted expansion is not from an algebra with less variables.")
  878. expansion_coeff = expansion.coeff
  879. new_coeff = np.zeros(self._dim_alg, dtype=self._var_type)
  880. coeff_old_algebra = np.zeros(expansion.dim_alg, dtype=self._var_type)
  881. if expansion.n_var == 1:
  882. # the monomials-coefficients mapping is trivial in the univariate case (hence no dedicated function)
  883. old_mapping = {(j,): j for j in range(0, expansion.order + 1)}
  884. else:
  885. old_mapping = expansion.get_mapping_monom()
  886. for old_exponent, old_index_var in old_mapping.items():
  887. coeff_old_algebra[:old_index_var] = 0.
  888. coeff_old_algebra[old_index_var] = 1.
  889. old_monomial = expansion.create_expansion_with_coeff(coeff_old_algebra)
  890. str_old_monomial = str(old_monomial).split(landau_symbol)[0]
  891. coeff_new_algebra = np.zeros(self._dim_alg, dtype=self._var_type)
  892. for new_exponent, new_index_var in self.get_mapping_monom().items():
  893. coeff_new_algebra[:new_index_var] = 0.
  894. if sum(new_exponent) == sum(old_exponent):
  895. coeff_new_algebra[new_index_var] = 1.
  896. str_new_monomial = str(self.create_expansion_with_coeff(coeff_new_algebra)).split(landau_symbol)[0]
  897. if str_new_monomial == str_old_monomial:
  898. new_coeff[new_index_var] = expansion_coeff[old_index_var]
  899. break
  900. else: # no break
  901. raise ValueError
  902. return self.create_expansion_with_coeff(new_coeff)
  903. @property
  904. def effective_order(self) -> int:
  905. """Method returning the effective order of the expansion, that is the highest order with non-zero coefficients.
  906. Returns:
  907. int: effective order of Taylor expansion.
  908. """
  909. for exponent, el in zip(reversed(list(self.get_mapping_monom().keys())), self._coeff[::-1]):
  910. if el != 0.:
  911. return sum(exponent)
  912. return 0
  913. @property
  914. def total_depth(self) -> int:
  915. """Method returning the total depth of the Taylor expansion, that is the lowest order with non-zero coefficient.
  916. Returns:
  917. int: total depth of Taylor expansion.
  918. """
  919. for exponents, index_coeff in self.get_mapping_monom().items():
  920. if self._coeff[index_coeff] != 0.:
  921. return sum(exponents)
  922. return self._order + 1
  923. @property
  924. def norm(self) -> float:
  925. """Method returning the norm of the Taylor expansion as defined by M. Berz. This quantity depends on the order.
  926. Returns:
  927. float: expansion's norm.
  928. """
  929. return np.max(np.abs(self._coeff)) * self._dim_alg
  930. def __eq__(self, other: Union["ComplexMultivarTaylor", complex, float]) -> bool:
  931. """Method enabling equality comparison with other Taylor expansions and scalars.
  932. Args:
  933. other (Union[ComplexMultivarTaylor, complex, float]): quantity to test equality on.
  934. Returns:
  935. bool: if the input is a Taylor expansion, returns True if it is in the same algebra and has the
  936. same coefficients. If the input is a scalar, checks if it equals the constant coefficients and if
  937. all the others are zero.
  938. """
  939. if isinstance(other, ComplexMultivarTaylor):
  940. if not self.is_in_same_algebra(other):
  941. return False
  942. # if expansions have same order and number of variables, then compare their coefficients
  943. return np.array_equal(self._coeff, other._coeff)
  944. # scalar case
  945. return self == self.create_const_expansion(other)