math_algebra.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. # math_algebra.py: collection of intrinsic functions for algebraic objects
  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 Callable, Union
  15. import math
  16. from math import pi
  17. import cmath
  18. import numpy as np
  19. from swiftt.taylor.real_multivar_taylor import RealMultivarTaylor
  20. from swiftt.taylor.taylor_map import RealTaylorMap
  21. from swiftt.taylor.taylor_expans_abstract import AlgebraicAbstract, TaylorExpansAbstract
  22. from swiftt.taylor.complex_univar_taylor import ComplexUnivarTaylor
  23. def taylor_real(func_taylor: Callable):
  24. def taylor_real_decorator(func_real: Callable):
  25. def function_wrapper(*args, **kwargs):
  26. p = args[0]
  27. if isinstance(p, RealMultivarTaylor):
  28. if p.is_trivial():
  29. return p.create_const_expansion(func_real(p.const))
  30. return func_taylor(p)
  31. return func_real(*args, **kwargs)
  32. function_wrapper.__name__ = func_real.__name__
  33. return function_wrapper
  34. return taylor_real_decorator
  35. def variance_from_mean(mean_func):
  36. def variance_wrapper(*args, **kwargs):
  37. mean_p = mean_func(*args, **kwargs)
  38. p, *rest = args
  39. # increase the maximum order by doubling it so that terms are not truncated later
  40. centered_p = p.prolong(2 * p.order) - mean_p
  41. return mean_func(centered_p ** 2, *rest, **kwargs)
  42. return variance_wrapper
  43. def covariance_from_mean(mean_func):
  44. def covariance_wrapper(*args, **kwargs):
  45. p, q, *rest = args
  46. mean_p = mean_func(p, *rest, **kwargs)
  47. mean_q = mean_func(q, *rest, **kwargs)
  48. # increase the maximum order by doubling it so that terms are not truncated later
  49. centered_p = p.prolong(2 * p.order) - mean_p
  50. centered_q = q.prolong(2 * q.order) - mean_q
  51. return mean_func(centered_p * centered_q, *rest, **kwargs)
  52. return covariance_wrapper
  53. def scalar_inversion(func: Callable[[ComplexUnivarTaylor], ComplexUnivarTaylor],
  54. p: ComplexUnivarTaylor) -> ComplexUnivarTaylor:
  55. """Method returning the (composition) inverse of the image by the input 1-D function of the input expansion. If the
  56. latter is the identity, the result is the expansion of the function's inverse, a.k.a. the truncated reverted series.
  57. Args:
  58. func (Callable[[ComplexUnivarTaylor]): univariate function.
  59. p (ComplexUnivarTaylor): Taylor expansion to be fed to function.
  60. Returns:
  61. ComplexUnivarTaylor: inverse of image expansion.
  62. """
  63. coeff = np.zeros(p.order + 1)
  64. coeff[0], coeff[1] = p.const, 1.
  65. univar_expansion = p.create_expansion_with_coeff(coeff)
  66. output = func(univar_expansion).get_nilpo_part().compo_inverse().compose(p.get_nilpo_part())
  67. output.const = coeff[0]
  68. return output
  69. def sqrt(p: Union[AlgebraicAbstract, complex, float]) -> Union[AlgebraicAbstract, complex, float]:
  70. """Taylor expansio- and interval--compatible version of the square root function.
  71. Wraps the math implementation for floats.
  72. Args:
  73. p (Union[AlgebraicAbstract, complex, float]): object whose square root needs to be computed.
  74. Returns:
  75. Union[AlgebraicAbstract, complex, float]: square root of input.
  76. """
  77. if isinstance(p, AlgebraicAbstract):
  78. return p.sqrt()
  79. if isinstance(p, (float, int)):
  80. return math.sqrt(p)
  81. if isinstance(p, complex):
  82. return cmath.sqrt(p)
  83. raise NotImplementedError
  84. def cbrt(p: Union[RealMultivarTaylor, float]) -> Union[RealMultivarTaylor, float]:
  85. """Taylor expansion- and interval-compatible version of the cubic root function.
  86. Wraps the math implementation for floats.
  87. Args:
  88. p (Union[RealMultivarTaylor, float]): object whose cubic root needs to be computed.
  89. Returns:
  90. Union[RealMultivarTaylor, float]: cubic root of input.
  91. """
  92. if isinstance(p, RealMultivarTaylor):
  93. return p.cbrt()
  94. if isinstance(p, (float, int)):
  95. return np.cbrt([p])[0]
  96. raise NotImplementedError
  97. def exp(p: Union[AlgebraicAbstract, complex, float]) -> Union[AlgebraicAbstract, complex, float]:
  98. """Taylor expansion- and interval-compatible version of the exponential function.
  99. Wraps the math implementation for complex and floats.
  100. Args:
  101. p (Union[AlgebraicAbstract, complex, float]): object whose exponential needs to be computed.
  102. Returns:
  103. Union[AlgebraicAbstract, complex, float]: exponential of input.
  104. """
  105. if isinstance(p, AlgebraicAbstract):
  106. return p.exp()
  107. if isinstance(p, (float, int)):
  108. return math.exp(p)
  109. if isinstance(p, complex):
  110. return cmath.exp(p)
  111. raise NotImplementedError
  112. def log(p: Union[AlgebraicAbstract, complex, float]) -> Union[AlgebraicAbstract, complex, float]:
  113. """Taylor expansion- and interval-compatible version of the natural logarithm function.
  114. Wraps the math implementation for complex and floats.
  115. Args:
  116. p (Union[AlgebraicAbstract, complex, float]): object whose natural logarithm needs to be computed.
  117. Returns:
  118. Union[AlgebraicAbstract, complex, float]: natural logarithm of input.
  119. """
  120. if isinstance(p, AlgebraicAbstract):
  121. return p.log()
  122. if isinstance(p, (float, int)):
  123. return math.log(p)
  124. if isinstance(p, complex):
  125. return cmath.log(p)
  126. raise NotImplementedError
  127. def log10(p: Union[AlgebraicAbstract, complex, float]) -> Union[AlgebraicAbstract, complex, float]:
  128. return log(p) * (1. / math.log(10.))
  129. def log2(p: Union[AlgebraicAbstract, complex, float]) -> Union[AlgebraicAbstract, complex, float]:
  130. return log(p) * (1. / math.log(2.))
  131. def cos(p: Union[TaylorExpansAbstract, complex, float]) -> Union[TaylorExpansAbstract, complex, float]:
  132. """Taylor expansion-compatible version of the cosine function. Wraps the math implementation for complex and floats.
  133. Args:
  134. p (Union[TaylorExpansAbstract, complex, float]): object whose cosine needs to be computed.
  135. Returns:
  136. Union[TaylorExpansAbstract, complex, float]: cosine of input.
  137. """
  138. if isinstance(p, TaylorExpansAbstract):
  139. return p.cos()
  140. if isinstance(p, (float, int)):
  141. return math.cos(p)
  142. if isinstance(p, complex):
  143. return cmath.cos(p)
  144. raise NotImplementedError
  145. def sin(p: Union[TaylorExpansAbstract, complex, float]) -> Union[TaylorExpansAbstract, complex, float]:
  146. """Taylor expansion-compatible version of the sine function. Wraps the math implementation for complex and floats.
  147. Args:
  148. p (Union[TaylorExpansAbstract, complex, float]): object whose sine needs to be computed.
  149. Returns:
  150. Union[TaylorExpansAbstract, complex, float]: sine of input.
  151. """
  152. if isinstance(p, TaylorExpansAbstract):
  153. return p.sin()
  154. if isinstance(p, (float, int)):
  155. return math.sin(p)
  156. if isinstance(p, complex):
  157. return cmath.sin(p)
  158. raise NotImplementedError
  159. def cosh(p: Union[TaylorExpansAbstract, complex, float]) -> Union[TaylorExpansAbstract, complex, float]:
  160. """Taylor expansion-compatible version of the hyperbolic cosine function.
  161. Wraps the math implementation for complex and floats.
  162. Args:
  163. p (Union[TaylorExpansAbstract, complex, float]): object whose hyperbolic cosine needs to be computed.
  164. Returns:
  165. Union[TaylorExpansAbstract, complex, float]: hyperbolic cosine of input.
  166. """
  167. if isinstance(p, TaylorExpansAbstract):
  168. return p.cosh()
  169. if isinstance(p, (float, int)):
  170. return math.cosh(p)
  171. if isinstance(p, complex):
  172. return cmath.cosh(p)
  173. raise NotImplementedError
  174. def sinh(p: Union[TaylorExpansAbstract, complex, float]) -> Union[TaylorExpansAbstract, complex, float]:
  175. """Taylor expansion-compatible version of the hyperbolic sine function.
  176. Wraps the math implementation for complex and floats.
  177. Args:
  178. p (Union[TaylorExpansAbstract, complex, float]): object whose hyperbolic sine needs to be computed.
  179. Returns:
  180. Union[TaylorExpansAbstract, complex, float]: hyperbolic sine of input.
  181. """
  182. if isinstance(p, TaylorExpansAbstract):
  183. return p.sinh()
  184. if isinstance(p, (float, int)):
  185. return math.sinh(p)
  186. if isinstance(p, complex):
  187. return cmath.sinh(p)
  188. raise NotImplementedError
  189. def tan(p: Union[TaylorExpansAbstract, complex, float]) -> Union[TaylorExpansAbstract, complex, float]:
  190. """Taylor expansion-compatible version of the tangent function.
  191. Wraps the math implementation for complex and floats.
  192. Args:
  193. p (Union[TaylorExpansAbstract, complex, float]): object whose tangent needs to be computed.
  194. Returns:
  195. Union[TaylorExpansAbstract, complex, float]: tangent of input.
  196. """
  197. if isinstance(p, TaylorExpansAbstract):
  198. return p.tan()
  199. if isinstance(p, (float, int)):
  200. return math.tan(p)
  201. if isinstance(p, complex):
  202. return cmath.tan(p)
  203. raise NotImplementedError
  204. def tanh(p: Union[TaylorExpansAbstract, complex, float]) -> Union[TaylorExpansAbstract, complex, float]:
  205. """Taylor expansion-compatible version of the hyperbolic tangent function.
  206. Wraps the math implementation for complex and floats.
  207. Args:
  208. p (Union[TaylorExpansAbstract, complex, float]): object whose hyperbolic tangent needs to be computed.
  209. Returns:
  210. Union[TaylorExpansAbstract, complex, float]: hyperbolic tangent of input.
  211. """
  212. if isinstance(p, TaylorExpansAbstract):
  213. return p.tanh()
  214. if isinstance(p, (float, int)):
  215. return math.tanh(p)
  216. if isinstance(p, complex):
  217. return cmath.tanh(p)
  218. raise NotImplementedError
  219. def atan(p: Union[RealMultivarTaylor, RealTaylorMap, float]) -> Union[RealMultivarTaylor, RealTaylorMap, float]:
  220. """Taylor expansion-compatible version of the inverse tangent function. Wraps the math implementation for floats.
  221. Args:
  222. p (Union[RealMultivarTaylor, RealTaylorMap, float]): object whose inverse tangent needs to be computed.
  223. Returns:
  224. Union[RealMultivarTaylor, RealTaylorMap, float]: inverse tangent of input.
  225. """
  226. if isinstance(p, (RealMultivarTaylor, RealTaylorMap)):
  227. return p.arctan()
  228. if isinstance(p, (float, int)):
  229. return math.atan(p)
  230. raise NotImplementedError
  231. def atanh(p: Union[RealMultivarTaylor, RealTaylorMap, float]) -> Union[RealMultivarTaylor, RealTaylorMap, float]:
  232. """Taylor expansion-compatible version of the inverse hyperbolic tangent function.
  233. Wraps the math implementation for floats.
  234. Args:
  235. p (Union[RealMultivarTaylor, RealTaylorMap, float]): object whose inverse hyperbolic tangent needs to
  236. be computed.
  237. Returns:
  238. Union[RealMultivarTaylor, RealTaylorMap, float]: inverse hyperbolic tangent of input.
  239. """
  240. if isinstance(p, (RealMultivarTaylor, RealTaylorMap)):
  241. return p.arctanh()
  242. if isinstance(p, (float, int)):
  243. return math.atanh(p)
  244. raise NotImplementedError
  245. def acos(p: Union[RealMultivarTaylor, RealTaylorMap, float]) -> Union[RealMultivarTaylor, RealTaylorMap, float]:
  246. """Taylor expansion-compatible version of the inverse cosine function. Wraps the math implementation for floats.
  247. Args:
  248. p (Union[RealMultivarTaylor, RealTaylorMap, float]): object whose inverse cosine needs to be computed.
  249. Returns:
  250. Union[RealMultivarTaylor, RealTaylorMap, float]: inverse cosine of input.
  251. """
  252. if isinstance(p, (RealMultivarTaylor, RealTaylorMap)):
  253. return p.arccos()
  254. if isinstance(p, (float, int)):
  255. return math.acos(p)
  256. raise NotImplementedError
  257. def asin(p: Union[RealMultivarTaylor, RealTaylorMap, float]) -> Union[RealMultivarTaylor, RealTaylorMap, float]:
  258. """Taylor expansion-compatible version of the inverse sine function. Wraps the math implementation for floats.
  259. Args:
  260. p (Union[RealMultivarTaylor, RealTaylorMap, float]): object whose inverse sine needs to be computed.
  261. Returns:
  262. Union[RealMultivarTaylor, RealTaylorMap, float]: inverse sine of input.
  263. """
  264. if isinstance(p, (RealMultivarTaylor, RealTaylorMap)):
  265. return p.arcsin()
  266. if isinstance(p, (float, int)):
  267. return math.asin(p)
  268. raise NotImplementedError
  269. def acosh(p: Union[RealMultivarTaylor, RealTaylorMap, float]) -> Union[RealMultivarTaylor, RealTaylorMap, float]:
  270. """Taylor expansion-compatible version of the inverse hyperbolic cosine function.
  271. Wraps the math implementation for floats.
  272. Args:
  273. p (Union[RealMultivarTaylor, RealTaylorMap, float]): object whose inverse hyperbolic cosine needs
  274. to be computed.
  275. Returns:
  276. Union[RealMultivarTaylor, RealTaylorMap, float]: inverse hyperbolic cosine of input.
  277. """
  278. if isinstance(p, (RealMultivarTaylor, RealTaylorMap)):
  279. return p.arccosh()
  280. if isinstance(p, (float, int)):
  281. return math.acosh(p)
  282. raise NotImplementedError
  283. def asinh(p: Union[RealMultivarTaylor, RealTaylorMap, float]) -> Union[RealMultivarTaylor, RealTaylorMap, float]:
  284. """Taylor expansion-compatible version of the inverse hyperbolic sine function.
  285. Wraps the math implementation for floats.
  286. Args:
  287. p (Union[RealMultivarTaylor, RealTaylorMap, float]): object whose inverse hyperbolic sine needs to be computed.
  288. Returns:
  289. Union[RealMultivarTaylor, RealTaylorMap, float]: inverse hyperbolic sine of input.
  290. """
  291. if isinstance(p, (RealMultivarTaylor, RealTaylorMap)):
  292. return p.arcsinh()
  293. if isinstance(p, (float, int)):
  294. return math.asinh(p)
  295. raise NotImplementedError
  296. def erf_taylor(p: RealMultivarTaylor) -> RealMultivarTaylor:
  297. """Version for Taylor expansions of the error function. Assumes the order is at least one.
  298. Args:
  299. p (RealMultivarTaylor): real Taylor expansion.
  300. Returns:
  301. RealMultivarTaylor: error function of input Taylor expansion.
  302. """
  303. order = p.order
  304. const = p.const
  305. seq = np.zeros(order)
  306. seq[0] = 2. * math.exp(-const**2) / math.sqrt(pi)
  307. if order > 1:
  308. seq[1] = -2. * seq[0] * const
  309. for i in range(2, order):
  310. seq[i] = -2. * (seq[i - 2] + const * seq[i - 1]) / i
  311. seq /= np.arange(1, order + 1)
  312. nilpo = p.get_nilpo_part()
  313. errorfun = seq[-1] * nilpo
  314. for el in seq[-2::-1]:
  315. errorfun.const = el
  316. errorfun *= nilpo
  317. errorfun.const = math.erf(const)
  318. return errorfun
  319. def erfc_taylor(p: RealMultivarTaylor) -> RealMultivarTaylor:
  320. """Version for Taylor expansions of the complementary error function. Assumes the order is at least one.
  321. Args:
  322. p (RealMultivarTaylor): real Taylor expansion.
  323. Returns:
  324. RealMultivarTaylor: complementary error function of input Taylor expansion.
  325. """
  326. return 1. - erf_taylor(p)
  327. def atan2(q: Union[RealMultivarTaylor, float], p: Union[RealMultivarTaylor, float]) -> Union[RealMultivarTaylor, float]:
  328. """Taylor expansion-compatible version of arctan 2. Wraps the math implementation for floats.
  329. Args:
  330. q (Union[RealMultivarTaylor, float]): real expansion.
  331. p (Union[RealMultivarTaylor, float]): real expansion.
  332. Returns:
  333. Union[RealMultivarTaylor, float]: arctan2 of (q, p).
  334. """
  335. if isinstance(q, RealMultivarTaylor):
  336. return q.arctan2(p)
  337. if isinstance(p, RealMultivarTaylor):
  338. return p.create_const_expansion(float(q)).arctan2(p)
  339. return math.atan2(q, p)
  340. def mean_from_normal(p: RealMultivarTaylor, mus, cov_mat) -> float:
  341. """ Assuming the variables of a Taylor expansion form a Gaussian random vector, compute its analytical mean. It uses
  342. the moment generating function of the normal law to obtain all the moments involved.
  343. Args:
  344. p (RealMultivarTaylor): Taylor expansion.
  345. mus (Iterable[float]): mean values of Gaussian vector.
  346. cov_mat(np.array): covariance matrix of random vector.
  347. Returns:
  348. float: mean value of the Taylor expansion obtained by evaluating its variables as the components of the
  349. inputted normal law.
  350. """
  351. # sanity checks
  352. if p.is_trivial():
  353. return p.const
  354. if len(mus) != p.n_var:
  355. raise ValueError("The inputted mean values do not have same dimension as number of variables in expansion.")
  356. if cov_mat.shape[0] != len(mus) or cov_mat.shape[1] != len(mus):
  357. raise ValueError("The inputted covariance matrix has wrong shape.")
  358. # build a new map of unknowns
  359. coeff = np.zeros(p.dim_alg)
  360. ts = []
  361. for i in range(0, p.n_var):
  362. coeff[i], coeff[i + 1] = 0., 1.
  363. ts.append(p.create_expansion_with_coeff(coeff))
  364. # compute moment generating function in algebra
  365. half_variances = np.diag(cov_mat) / 2.
  366. rhs = (mus[0] + half_variances[0] * ts[0]) * ts[0]
  367. for i, (mu, t, half_var) in enumerate(zip(mus[1:], ts[1:], half_variances[1:]), 1):
  368. inter = t.linearly_combine_with_many(half_var, ts[:i], cov_mat[i, :i])
  369. rhs += (mu + inter) * t
  370. moment_generating_func = exp(rhs)
  371. # compute mean by linearity i.e. adding all the contributions (coefficient times mean of monomial)
  372. mean = p.const
  373. coeff = p.coeff
  374. mapping = dict(ts[0].get_mapping_monom())
  375. del mapping[tuple([0] * ts[0].n_var)]
  376. for exponents, index_coeff in mapping.items():
  377. if coeff[index_coeff] != 0.:
  378. mean += coeff[index_coeff] * moment_generating_func.get_partial_deriv(exponents)
  379. return mean
  380. def mean_from_uniform(p: RealMultivarTaylor, lbs, ubs) -> float:
  381. """ Assuming the variables of a Taylor expansion form a uniformly distributed random vector, compute its analytical
  382. mean. It uses a closed form expression for all the moments involved.
  383. Args:
  384. p (RealMultivarTaylor): Taylor expansion.
  385. lbs (Iterable[float]): lower bounds of independent uniform distributions.
  386. ubs (Iterable[float]): upper bounds of independent uniform distributions.
  387. Returns:
  388. float: mean value of the Taylor expansion obtained by evaluating its variables as the components of the
  389. inputted uniform law.
  390. """
  391. # sanity checks
  392. if p.is_trivial():
  393. return p.const
  394. if len(lbs) != p.n_var:
  395. raise ValueError("The inputted lower bounds do not have same dimension as number of variables in expansion.")
  396. if len(ubs) != len(lbs):
  397. raise ValueError("The size of the upper bounds does not match the one of the lower bounds.")
  398. if len(ubs[ubs <= lbs]) != 0:
  399. raise ValueError("At least one upper bound is lower than lower bound.")
  400. moments = np.ones((len(ubs), p.order))
  401. for i, (lb, ub) in enumerate(zip(lbs, ubs)):
  402. if lb != 0.:
  403. ratio = power_ratio = ub / lb
  404. power_lb = lb
  405. summed = 1. + power_ratio
  406. moments[i, 0] = summed * power_lb
  407. for j in range(1, p.order):
  408. power_lb *= lb
  409. power_ratio *= ratio
  410. summed += power_ratio
  411. moments[i, j] = summed * power_lb
  412. else:
  413. moments[i, :] = np.cumprod(np.full(p.order, ub))
  414. moments[i, :] /= np.arange(2., p.order + 2., 1.)
  415. # compute mean by linearity i.e. adding all the contributions (coefficient times mean of monomial)
  416. mean = p.const
  417. coeff = p.coeff
  418. mapping = dict(p.get_mapping_monom())
  419. del mapping[tuple([0] * p.n_var)]
  420. for exponents, index_coeff in mapping.items():
  421. if coeff[index_coeff] != 0.:
  422. inter = 1.
  423. for j, el in enumerate(exponents):
  424. if el != 0:
  425. inter *= moments[j, el - 1]
  426. mean += coeff[index_coeff] * inter
  427. return mean
  428. # define (co)variance functions from the mean
  429. var_from_normal = variance_from_mean(mean_from_normal)
  430. covar_from_normal = covariance_from_mean(mean_from_normal)
  431. var_from_uniform = variance_from_mean(mean_from_uniform)
  432. covar_from_uniform = covariance_from_mean(mean_from_uniform)
  433. # wrap functions from module math
  434. arccos = acos
  435. arcsin = asin
  436. arctan = atan
  437. arccosh = acosh
  438. arcsinh = asinh
  439. arctanh = atanh
  440. erf = taylor_real(erf_taylor)(math.erf)
  441. erfc = taylor_real(erfc_taylor)(math.erfc)