123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- # tables.py: collection of methods to pre-computes tables for a given Taylor algebra
- # Copyright 2022 Romain Serra
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- from functools import lru_cache
- from typing import Dict, Tuple, List
- import numpy as np
- _lru_cache_maxsize = None
- @lru_cache(maxsize=_lru_cache_maxsize)
- def factorials(up_to: int) -> np.ndarray:
- """Memoized method to compute factorials.
- Args:
- up_to (int): maximum order of factorials.
- Returns:
- numpy.ndarray: all factorials up to inputted order.
- """
- if up_to <= 1:
- return np.ones(up_to + 1, dtype=int)
- inter = factorials(up_to - 1) # recursive call
- return np.append(inter, [up_to * inter[-1]])
- @lru_cache(maxsize=_lru_cache_maxsize)
- def algebra_dim(n_var: int, order: int) -> int:
- """Memoized method to compute the dimension of a given algebra i.e. the number of coefficients in the polynomial
- part.
- Args:
- n_var (int): number of variables.
- order (int): order of expansion.
- Returns:
- int: dimension of Taylor algebra.
- """
- if order > n_var:
- return algebra_dim(order, n_var) # use symmetry
- if order == 0:
- return 1
- # recursive call with smaller order
- return int(algebra_dim(n_var, order - 1) * (n_var + order) / order)
- @lru_cache(maxsize=_lru_cache_maxsize)
- def mapping_monom(n_var: int, order: int) -> Dict[Tuple[int, ...], int]:
- """Memoized method to build mapping between the indices of the polynomial coefficients and the monomials.
- It assumes that the initial order of a dictionary is kept, which is only the case for Python 3.6 or newer.
- It raises an error if called for univariate expansions as it is not needed in that case.
- Args:
- n_var (int): number of variables.
- order (int): order of expansion.
- Returns:
- Dict[Tuple[int, ...], int]: mapping where keys are the monomials (tuple of order for each variable)
- and values are the polynomial coefficients' indices.
- """
- if n_var == 1:
- raise ValueError("Such dictionary is not helpful with univariate expansions.")
- if n_var == 2:
- mapping = {(0, 0): 0}
- for i in range(1, order + 1):
- for j in range(0, i + 1):
- mapping[(i - j, j)] = len(mapping)
- # keys i.e. monomials are already sorted
- else:
- # at least three variables
- mapping = {}
- # recursive call with one less variable
- mapping_less_variables = mapping_monom(n_var - 1, order)
- monomial_orders = np.sum(np.array(list(mapping_less_variables.keys())), axis=1)
- # complete existing exponents with exponent in last variable
- for exponent, index_coeff in mapping_less_variables.items():
- for i in range(0, order + 1 - monomial_orders[index_coeff]):
- mapping[exponent + (i,)] = len(mapping)
- # sort monomials in graded lexicographic order a.k.a. grlex (that is first in increasing total degree,
- # then in decreasing degree in x1, then in x2, etc.)
- sorted_monomials = sorted(mapping.keys(), key=lambda x: (-sum(x), *x), reverse=True)
- mapping = {key: value for value, key in enumerate(sorted_monomials)}
- return mapping
- @lru_cache(maxsize=_lru_cache_maxsize)
- def deriv_table(n_var: int, order: int) -> np.ndarray:
- """Memoized method to build differentiation/integration table for given order and number of variables.
- It raises an error if called for univariate expansions as it is not needed in that case.
- Args:
- n_var (int): number of variables.
- order (int): order of expansion.
- Returns:
- numpy.ndarray: differentiation/integration table.
- """
- if n_var == 1:
- raise ValueError("Such table is not helpful with univariate expansions.")
- mapping_indices = mapping_monom(n_var, order)
- dim = len(mapping_indices)
- table_deriv = np.full((dim, n_var), dim, dtype=int)
- for i, exponent in enumerate(list(mapping_indices.keys())[:algebra_dim(n_var, order - 1)]):
- for index_var, power_var in enumerate(exponent):
- new_tuple = exponent[:index_var] + (power_var + 1,) + exponent[index_var + 1:]
- table_deriv[i, index_var] = mapping_indices[new_tuple]
- return table_deriv
- @lru_cache(maxsize=_lru_cache_maxsize)
- def mul_table(n_var: int, order: int) -> List[List[int]]:
- """Memoized method to build multiplication table for given order and number of variables. On a given row,
- corresponding to a monomial M as ordered in the mapping with the coefficients, the elements are the indices of the
- monomials obtained when multiplying M with all the monomials before M in the map. There is no row for monomials
- x_i**order as it is known that only the monomial 1 can be multiplied with them.
- It raises an error if called for univariate expansions as it is not needed in that case.
- Args:
- n_var (int): number of variables.
- order (int): order of expansion.
- Returns:
- List[List[int]]: multiplication table.
- """
- if n_var == 1:
- raise ValueError("Such table is not helpful with univariate expansions.")
- table = [[0]]
- if order > 0:
- mapping_indices = mapping_monom(n_var, order)
- monomial_list = list(mapping_indices.keys())
- monomial_orders = np.sum(np.array(monomial_list), axis=1)
- for i, exponent_i in enumerate(monomial_list[1:algebra_dim(n_var, order - 1)], 1):
- table_row = []
- orders_i_plus_j = monomial_orders[:i] + monomial_orders[i]
- for j, exponent_j in enumerate(monomial_list[:i]): # use symmetry between i and j
- if orders_i_plus_j[j] <= order:
- summed_expo = tuple(index_i + index_j for index_i, index_j in zip(exponent_i, exponent_j))
- table_row.append(mapping_indices[summed_expo])
- else:
- break
- table.append(table_row)
- return table
- @lru_cache(maxsize=_lru_cache_maxsize)
- def flat_mul_table(n_var: int, order: int) -> np.ndarray:
- """Memoized method to flattened algebra's multiplication table.
- It raises an error if called for univariate expansions as it is not needed in that case.
- Args:
- n_var (int): number of variables.
- order (int): order of expansion.
- Returns:
- numpy.ndarray: flattened multiplication table.
- """
- return np.hstack(mul_table(n_var, order))
- @lru_cache(maxsize=_lru_cache_maxsize)
- def mul_indices(n_var: int, order: int) -> np.ndarray:
- """Memoized method to precompute array indices for multiplication.
- Args:
- n_var (int): number of variables.
- order (int): order of expansion.
- Returns:
- numpy.ndarray: array indices used in multiplication.
- """
- indices = [len(row) for row in mul_table(n_var, order)]
- return np.cumsum(indices)
- @lru_cache(maxsize=_lru_cache_maxsize)
- def square_indices(n_var: int, order: int) -> np.ndarray:
- """Memoized method to precompute square indices i.e. indices of monomials that are the square of another one in the
- algebra. Those are not already contained in the result of mul_table to save memory.
- Args:
- n_var (int): number of variables.
- order (int): order of expansion.
- Returns:
- numpy.ndarray: so-called square indices i.e. indices of monomials which are the square of a monomial of the
- algebra.
- """
- dim_half_order = algebra_dim(n_var, order // 2)
- indices = np.zeros(dim_half_order, dtype=int)
- mapping_indices = mapping_monom(n_var, order)
- for i, exponent_i in enumerate(list(mapping_indices.keys())[1:dim_half_order], 1):
- indices[i] = mapping_indices[tuple(2 * el for el in exponent_i)]
- return indices
|