tables.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. # tables.py: collection of methods to pre-computes tables for a given Taylor algebra
  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 typing import Dict, Tuple, List
  16. import numpy as np
  17. _lru_cache_maxsize = None
  18. @lru_cache(maxsize=_lru_cache_maxsize)
  19. def factorials(up_to: int) -> np.ndarray:
  20. """Memoized method to compute factorials.
  21. Args:
  22. up_to (int): maximum order of factorials.
  23. Returns:
  24. numpy.ndarray: all factorials up to inputted order.
  25. """
  26. if up_to <= 1:
  27. return np.ones(up_to + 1, dtype=int)
  28. inter = factorials(up_to - 1) # recursive call
  29. return np.append(inter, [up_to * inter[-1]])
  30. @lru_cache(maxsize=_lru_cache_maxsize)
  31. def algebra_dim(n_var: int, order: int) -> int:
  32. """Memoized method to compute the dimension of a given algebra i.e. the number of coefficients in the polynomial
  33. part.
  34. Args:
  35. n_var (int): number of variables.
  36. order (int): order of expansion.
  37. Returns:
  38. int: dimension of Taylor algebra.
  39. """
  40. if order > n_var:
  41. return algebra_dim(order, n_var) # use symmetry
  42. if order == 0:
  43. return 1
  44. # recursive call with smaller order
  45. return int(algebra_dim(n_var, order - 1) * (n_var + order) / order)
  46. @lru_cache(maxsize=_lru_cache_maxsize)
  47. def mapping_monom(n_var: int, order: int) -> Dict[Tuple[int, ...], int]:
  48. """Memoized method to build mapping between the indices of the polynomial coefficients and the monomials.
  49. It assumes that the initial order of a dictionary is kept, which is only the case for Python 3.6 or newer.
  50. It raises an error if called for univariate expansions as it is not needed in that case.
  51. Args:
  52. n_var (int): number of variables.
  53. order (int): order of expansion.
  54. Returns:
  55. Dict[Tuple[int, ...], int]: mapping where keys are the monomials (tuple of order for each variable)
  56. and values are the polynomial coefficients' indices.
  57. """
  58. if n_var == 1:
  59. raise ValueError("Such dictionary is not helpful with univariate expansions.")
  60. if n_var == 2:
  61. mapping = {(0, 0): 0}
  62. for i in range(1, order + 1):
  63. for j in range(0, i + 1):
  64. mapping[(i - j, j)] = len(mapping)
  65. # keys i.e. monomials are already sorted
  66. else:
  67. # at least three variables
  68. mapping = {}
  69. # recursive call with one less variable
  70. mapping_less_variables = mapping_monom(n_var - 1, order)
  71. monomial_orders = np.sum(np.array(list(mapping_less_variables.keys())), axis=1)
  72. # complete existing exponents with exponent in last variable
  73. for exponent, index_coeff in mapping_less_variables.items():
  74. for i in range(0, order + 1 - monomial_orders[index_coeff]):
  75. mapping[exponent + (i,)] = len(mapping)
  76. # sort monomials in graded lexicographic order a.k.a. grlex (that is first in increasing total degree,
  77. # then in decreasing degree in x1, then in x2, etc.)
  78. sorted_monomials = sorted(mapping.keys(), key=lambda x: (-sum(x), *x), reverse=True)
  79. mapping = {key: value for value, key in enumerate(sorted_monomials)}
  80. return mapping
  81. @lru_cache(maxsize=_lru_cache_maxsize)
  82. def deriv_table(n_var: int, order: int) -> np.ndarray:
  83. """Memoized method to build differentiation/integration table for given order and number of variables.
  84. It raises an error if called for univariate expansions as it is not needed in that case.
  85. Args:
  86. n_var (int): number of variables.
  87. order (int): order of expansion.
  88. Returns:
  89. numpy.ndarray: differentiation/integration table.
  90. """
  91. if n_var == 1:
  92. raise ValueError("Such table is not helpful with univariate expansions.")
  93. mapping_indices = mapping_monom(n_var, order)
  94. dim = len(mapping_indices)
  95. table_deriv = np.full((dim, n_var), dim, dtype=int)
  96. for i, exponent in enumerate(list(mapping_indices.keys())[:algebra_dim(n_var, order - 1)]):
  97. for index_var, power_var in enumerate(exponent):
  98. new_tuple = exponent[:index_var] + (power_var + 1,) + exponent[index_var + 1:]
  99. table_deriv[i, index_var] = mapping_indices[new_tuple]
  100. return table_deriv
  101. @lru_cache(maxsize=_lru_cache_maxsize)
  102. def mul_table(n_var: int, order: int) -> List[List[int]]:
  103. """Memoized method to build multiplication table for given order and number of variables. On a given row,
  104. corresponding to a monomial M as ordered in the mapping with the coefficients, the elements are the indices of the
  105. monomials obtained when multiplying M with all the monomials before M in the map. There is no row for monomials
  106. x_i**order as it is known that only the monomial 1 can be multiplied with them.
  107. It raises an error if called for univariate expansions as it is not needed in that case.
  108. Args:
  109. n_var (int): number of variables.
  110. order (int): order of expansion.
  111. Returns:
  112. List[List[int]]: multiplication table.
  113. """
  114. if n_var == 1:
  115. raise ValueError("Such table is not helpful with univariate expansions.")
  116. table = [[0]]
  117. if order > 0:
  118. mapping_indices = mapping_monom(n_var, order)
  119. monomial_list = list(mapping_indices.keys())
  120. monomial_orders = np.sum(np.array(monomial_list), axis=1)
  121. for i, exponent_i in enumerate(monomial_list[1:algebra_dim(n_var, order - 1)], 1):
  122. table_row = []
  123. orders_i_plus_j = monomial_orders[:i] + monomial_orders[i]
  124. for j, exponent_j in enumerate(monomial_list[:i]): # use symmetry between i and j
  125. if orders_i_plus_j[j] <= order:
  126. summed_expo = tuple(index_i + index_j for index_i, index_j in zip(exponent_i, exponent_j))
  127. table_row.append(mapping_indices[summed_expo])
  128. else:
  129. break
  130. table.append(table_row)
  131. return table
  132. @lru_cache(maxsize=_lru_cache_maxsize)
  133. def flat_mul_table(n_var: int, order: int) -> np.ndarray:
  134. """Memoized method to flattened algebra's multiplication table.
  135. It raises an error if called for univariate expansions as it is not needed in that case.
  136. Args:
  137. n_var (int): number of variables.
  138. order (int): order of expansion.
  139. Returns:
  140. numpy.ndarray: flattened multiplication table.
  141. """
  142. return np.hstack(mul_table(n_var, order))
  143. @lru_cache(maxsize=_lru_cache_maxsize)
  144. def mul_indices(n_var: int, order: int) -> np.ndarray:
  145. """Memoized method to precompute array indices for multiplication.
  146. Args:
  147. n_var (int): number of variables.
  148. order (int): order of expansion.
  149. Returns:
  150. numpy.ndarray: array indices used in multiplication.
  151. """
  152. indices = [len(row) for row in mul_table(n_var, order)]
  153. return np.cumsum(indices)
  154. @lru_cache(maxsize=_lru_cache_maxsize)
  155. def square_indices(n_var: int, order: int) -> np.ndarray:
  156. """Memoized method to precompute square indices i.e. indices of monomials that are the square of another one in the
  157. algebra. Those are not already contained in the result of mul_table to save memory.
  158. Args:
  159. n_var (int): number of variables.
  160. order (int): order of expansion.
  161. Returns:
  162. numpy.ndarray: so-called square indices i.e. indices of monomials which are the square of a monomial of the
  163. algebra.
  164. """
  165. dim_half_order = algebra_dim(n_var, order // 2)
  166. indices = np.zeros(dim_half_order, dtype=int)
  167. mapping_indices = mapping_monom(n_var, order)
  168. for i, exponent_i in enumerate(list(mapping_indices.keys())[1:dim_half_order], 1):
  169. indices[i] = mapping_indices[tuple(2 * el for el in exponent_i)]
  170. return indices