├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── orthnet ├── __init__.py ├── backend │ ├── __init__.py │ ├── _backend.py │ ├── _numpy.py │ ├── _tensorflow.py │ └── _torch.py ├── poly │ ├── __init__.py │ ├── _chebyshev.py │ ├── _hermite.py │ ├── _jacobi.py │ ├── _laguerre.py │ ├── _legendre.py │ └── polynomial.py └── utils │ ├── __init__.py │ ├── check_backend.py │ ├── enum_dim │ ├── compile.sh │ ├── enum_dim.h │ ├── enum_dim.i │ └── poly_class.py │ └── timeit.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .ipynb_checkpoints/ 3 | test/ 4 | dist/ 5 | MANIFEST 6 | old_orthnet/ 7 | build/ 8 | orthnet/utils/_enum_dim.* 9 | orthnet/utils/enum_dim/enum_dim.py 10 | orthnet/utils/enum_dim/enum_dim_wrap.cpp 11 | old_demo/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chuan Lu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | recursive-include demo * 4 | recursive-include orthnet * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OrthNet 2 | TensorFlow, PyTorch and Numpy layers for generating multi-dimensional Orthogonal Polynomials 3 | 4 | 5 | [1. Installation](#installation) 6 | [2. Usage](#usage) 7 | [3. Polynomials](#polynomials) 8 | [4. Base Class(Poly)](#base-class) 9 | 10 | 11 | ## Installation: 12 | 1. the stable version: 13 | `pip3 install orthnet` 14 | 15 | 2. the dev version: 16 | ``` 17 | git clone https://github.com/orcuslc/orthnet.git && cd orthnet 18 | python3 setup.py build_ext --inplace && python3 setup.py install 19 | ``` 20 | 21 | ## Usage: 22 | ### with TensorFlow 23 | ```python 24 | import tensorflow as tf 25 | import numpy as np 26 | from orthnet import Legendre 27 | 28 | x_data = np.random.random((10, 2)) 29 | x = tf.placeholder(dtype = tf.float32, shape = [None, 2]) 30 | L = Legendre(x, 5) 31 | 32 | with tf.Session() as sess: 33 | print(L.tensor, feed_dict = {x: x_data}) 34 | ``` 35 | 36 | ### with PyTorch 37 | ```python 38 | import torch 39 | import numpy as np 40 | from orthnet import Legendre 41 | 42 | x = torch.DoubleTensor(np.random.random((10, 2))) 43 | L = Legendre(x, 5) 44 | print(L.tensor) 45 | ``` 46 | 47 | ### with Numpy 48 | ```python 49 | import numpy as np 50 | from orthnet import Legendre 51 | 52 | x = np.random.random((10, 2)) 53 | L = Legendre(x, 5) 54 | print(L.tensor) 55 | ``` 56 | 57 | ### Specify Backend 58 | In some scenarios, users can specify the exact backend compatible with the input `x`. The backends provided are: 59 | - [`orthnet.TensorflowBackend()`](./orthnet/backend/_tensorflow.py) 60 | - [`orthnet.TorchBackend()`](./orthnet/backend/_torch.py) 61 | - [`orthnet.NumpyBackend()`](./orthnet/backend/_numpy.py) 62 | 63 | An example to specify the backend is as follows. 64 | ```python 65 | import numpy as np 66 | from orthnet import Legendre, NumpyBackend 67 | 68 | x = np.random.random((10, 2)) 69 | L = Legendre(x, 5, backend = NumpyBackend()) 70 | print(L.tensor) 71 | ``` 72 | 73 | ### Specify tensor product combinations 74 | In some scenarios, users may provide pre-computed tensor product combinations to save computing time. An example of providing combinations is as follows. 75 | ```python 76 | import numpy as np 77 | from orthnet import Legendre, enum_dim 78 | 79 | dim = 2 80 | degree = 5 81 | x = np.random.random((10, dim)) 82 | L = Legendre(x, degree, combinations = enum_dim(degree, dim)) 83 | print(L.tensor) 84 | ``` 85 | 86 | ## Polynomials: 87 | | Class | Polynomial | 88 | |-------|-----------| 89 | | [`orthnet.Legendre(Poly)`](./orthnet/poly/_legendre.py) | [Legendre polynomial](https://en.wikipedia.org/wiki/Legendre_polynomials) |   90 | | [`orthnet.Legendre_Normalized(Poly)`](./orthnet/poly/_legendre.py) | [Normalized Legendre polynomial](https://en.wikipedia.org/w/index.php?title=Legendre_polynomials§ion=6#Additional_properties_of_Legendre_polynomials) | 91 | | [`orthnet.Laguerre(Poly)`](./orthnet/poly/_laguerre.py) | [Laguerre polynomial](https://en.wikipedia.org/wiki/Laguerre_polynomials) |   92 | | [`orthnet.Hermite(Poly)`](./orthnet/poly/_hermite.py) | [Hermite polynomial of the first kind (in probability theory)](https://en.wikipedia.org/wiki/Hermite_polynomials) | 93 | | [`orthnet.Hermite2(Poly)`](./orthnet/poly/_hermite.py) | [Hermite polynomial of the second kind (in physics)](https://en.wikipedia.org/wiki/Hermite_polynomials) | 94 | | [`orthnet.Chebyshev(Poly)`](./orthnet/poly/_chebyshev.py) | [Chebyshev polynomial of the first kind](https://en.wikipedia.org/wiki/Chebyshev_polynomials) | 95 | | [`orthnet.Chebyshev2(Poly)`](./orthnet/poly/_chebyshev.py) | [Chebyshev polynomial of the second kind](https://en.wikipedia.org/wiki/Chebyshev_polynomials) | 96 | | [`orthnet.Jacobi(Poly, alpha, beta)`](./orthnet/poly/_jacobi.py) | [Jacobi polynomial](https://en.wikipedia.org/wiki/Jacobi_polynomials) | 97 | 98 | 99 | ## Base class: 100 | Class [`Poly(x, degree, combination = None)`](./orthnet/poly/polynomial.py): 101 | - Inputs: 102 | + `x` a tensor 103 | + `degree` highest degree for target polynomials 104 | + `combination` optional, tensor product combinations 105 | - Attributes: 106 | + `Poly.tensor` the tensor of function values (with degree from 0 to `Poly.degree`(included)) 107 | + `Poly.length` the number of function basis (columns) in `Poly.tensor` 108 | + `Poly.index` the index of the first combination of each degree in `Poly.combinations` 109 | + `Poly.combinations` all combinations of tensor product 110 | + `Poly.tensor_of_degree(degree)` return all polynomials of given degrees 111 | + `Poly.eval(coefficients)` return the function values with given coefficients 112 | + `Poly.quadrature(function, weight)` return Gauss quadrature with given function and weight 113 | -------------------------------------------------------------------------------- /orthnet/__init__.py: -------------------------------------------------------------------------------- 1 | from .poly import * 2 | from .utils import enum_dim 3 | from .backend import * -------------------------------------------------------------------------------- /orthnet/backend/__init__.py: -------------------------------------------------------------------------------- 1 | from ._tensorflow import TensorflowBackend 2 | from ._torch import TorchBackend 3 | from ._numpy import NumpyBackend 4 | 5 | __all__ = ["TensorflowBackend", "TorchBackend", "NumpyBackend"] -------------------------------------------------------------------------------- /orthnet/backend/_backend.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | def assert_backend_available(f): 4 | @wraps(f) 5 | def check(backend, *args, **kw): 6 | if not backend.is_available: 7 | raise RuntimeError( 8 | "Backend `{1}` is not available".format(str(backend))) 9 | return f(backend, *args, **kw) 10 | return check 11 | 12 | 13 | class Backend(object): 14 | def __str__(self): 15 | return "" 16 | 17 | def __false(self): 18 | return False 19 | 20 | is_available = is_compatible = __false 21 | 22 | def concatenate(self, tensor, axis): 23 | return None 24 | 25 | def ones_like(self, tensor): 26 | return None 27 | 28 | def multiply(self, x, y): 29 | return None 30 | 31 | def expand_dims(self, tensor, axis): 32 | return None 33 | 34 | def get_dims(self, tensor): 35 | return None 36 | 37 | def reshape(self, tensor, shape): 38 | return None 39 | 40 | def matmul(self, tensor1, tensor2): 41 | return None -------------------------------------------------------------------------------- /orthnet/backend/_numpy.py: -------------------------------------------------------------------------------- 1 | """ 2 | numpy backend 3 | """ 4 | try: 5 | import numpy as np 6 | except ImportError: 7 | np = None 8 | 9 | from ._backend import Backend, assert_backend_available 10 | 11 | 12 | class NumpyBackend(Backend): 13 | 14 | def __str__(self): 15 | return "numpy" 16 | 17 | def is_available(self): 18 | return np is not None 19 | 20 | @assert_backend_available 21 | def is_compatible(self, args): 22 | if list(filter(lambda t: isinstance(args, t), [ 23 | np.ndarray, 24 | np.matrix 25 | ])) != []: 26 | return True 27 | # , "numpy backend requires input to be an instance of `np.ndarray` or `np.matrix`" 28 | return False 29 | 30 | def concatenate(self, tensor, axis): 31 | return np.concatenate(tensor, axis = axis) 32 | 33 | def ones_like(self, tensor): 34 | return np.ones_like(tensor) 35 | 36 | def multiply(self, x, y): 37 | return x*y 38 | 39 | def expand_dims(self, tensor, axis): 40 | return np.expand_dims(tensor, axis) 41 | 42 | def get_dims(self, tensor): 43 | return tensor.shape 44 | 45 | def reshape(self, tensor, shape): 46 | return np.reshape(tensor, shape) 47 | 48 | def matmul(self, tensor1, tensor2): 49 | return np.dot(tensor1, tensor2) -------------------------------------------------------------------------------- /orthnet/backend/_tensorflow.py: -------------------------------------------------------------------------------- 1 | """ 2 | tensorflow backend 3 | """ 4 | try: 5 | import tensorflow as tf 6 | except ImportError: 7 | tf = None 8 | 9 | from ._backend import Backend, assert_backend_available 10 | 11 | 12 | class TensorflowBackend(Backend): 13 | 14 | def __str__(self): 15 | return "tensorflow" 16 | 17 | def is_available(self): 18 | return tf is not None 19 | 20 | @assert_backend_available 21 | def is_compatible(self, args): 22 | if list(filter(lambda t: isinstance(args, t), [ 23 | tf.Tensor, 24 | tf.Variable 25 | ])) != []: 26 | return True 27 | # "tensorflow backend requires input to be an isinstance of `tensorflow.Tensor` or `tensorflow.Variable`" 28 | return False 29 | 30 | def concatenate(self, tensor, axis): 31 | return tf.concat(tensor, axis = axis) 32 | 33 | def ones_like(self, tensor): 34 | return tf.ones_like(tensor) 35 | 36 | def multiply(self, x, y): 37 | return tf.multiply(x, y) 38 | 39 | def expand_dims(self, tensor, axis): 40 | return tf.expand_dims(tensor, axis) 41 | 42 | def get_dims(self, tensor): 43 | return [dim.value for dim in tensor.get_shape()] 44 | 45 | def reshape(self, tensor, shape): 46 | return tf.reshape(tensor, shape) 47 | 48 | def matmul(self, tensor1, tensor2): 49 | return tf.matmul(tensor1, tensor2) 50 | -------------------------------------------------------------------------------- /orthnet/backend/_torch.py: -------------------------------------------------------------------------------- 1 | """ 2 | torch backend 3 | """ 4 | try: 5 | import torch 6 | except ImportError: 7 | torch = None 8 | 9 | from ._backend import Backend, assert_backend_available 10 | 11 | 12 | class TorchBackend(Backend): 13 | 14 | def __str__(self): 15 | return "torch" 16 | 17 | def is_available(self): 18 | return torch is not None 19 | 20 | @assert_backend_available 21 | def is_compatible(self, args): 22 | if list(filter(lambda t: isinstance(args, t), [ 23 | torch.FloatTensor, 24 | torch.DoubleTensor, 25 | torch.cuda.FloatTensor, 26 | torch.cuda.DoubleTensor 27 | ])) != []: 28 | return True 29 | # , "torch backend requires input to be an instance of `torch.FloatTensor`, `torch.DoubleTensor`, `torch.cuda.FloatTensor` or `torch.cuda.DoubleTensor`" 30 | return False 31 | 32 | def concatenate(self, tensor, axis): 33 | return torch.cat(tensor, dim = axis) 34 | 35 | def ones_like(self, tensor): 36 | return torch.ones_like(tensor) 37 | 38 | def multiply(self, x, y): 39 | return torch.mul(x, y) 40 | 41 | def expand_dims(self, tensor, axis): 42 | return tensor.unsqueeze(axis) 43 | 44 | def get_dims(self, tensor): 45 | return tensor.size() 46 | 47 | def reshape(self, tensor, shape): 48 | return tensor.view(shape) 49 | 50 | def matmul(self, tensor1, tensor2): 51 | return torch.matmul(tensor1, tensor2) -------------------------------------------------------------------------------- /orthnet/poly/__init__.py: -------------------------------------------------------------------------------- 1 | from ._legendre import Legendre, Legendre_Normalized 2 | from ._laguerre import Laguerre 3 | from ._hermite import Hermite, Hermite2 4 | from ._chebyshev import Chebyshev, Chebyshev2 5 | from ._jacobi import Jacobi 6 | 7 | __all__ = ['Legendre', 'Legendre_Normalized', 'Laguerre', 'Hermite', 'Hermite2', 'Chebyshev', 'Chebyshev2', 'Jacobi'] -------------------------------------------------------------------------------- /orthnet/poly/_chebyshev.py: -------------------------------------------------------------------------------- 1 | from ..utils import check_backend 2 | from .polynomial import Poly 3 | 4 | class Chebyshev(Poly): 5 | """ 6 | Chebyshev polynomials of the fist kind 7 | """ 8 | def __init__(self, x, degree, backend = None, **kw): 9 | """ 10 | input: 11 | - x: a tensor 12 | - degree: highest degree of polynomial 13 | """ 14 | if backend is None: 15 | self._backend = check_backend(x) 16 | else: 17 | self._backend = backend 18 | initial = [lambda x: self._backend.ones_like(x), lambda x: x] 19 | recurrence = lambda p1, p2, n, x: self._backend.multiply(x, p1)*2 - p2 20 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) 21 | 22 | class Chebyshev2(Poly): 23 | """ 24 | Chebyshev polynomials of the second kind 25 | """ 26 | def __init__(self, x, degree, backend = None, **kw): 27 | """ 28 | input: 29 | - x: a tensor 30 | - degree: highest degree of polynomial 31 | """ 32 | if backend is None: 33 | self._backend = check_backend(x) 34 | else: 35 | self._backend = backend 36 | initial = [lambda x: self._backend.ones_like(x), lambda x: x*2] 37 | recurrence = lambda p1, p2, n, x: self._backend.multiply(x, p1)*2 - p2 38 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) -------------------------------------------------------------------------------- /orthnet/poly/_hermite.py: -------------------------------------------------------------------------------- 1 | from ..utils import check_backend 2 | from .polynomial import Poly 3 | 4 | class Hermite(Poly): 5 | """ 6 | Hermite polynomials of the first kind (in probability theory) 7 | """ 8 | def __init__(self, x, degree, backend = None, **kw): 9 | """ 10 | input: 11 | - x: a tensor 12 | - degree: highest degree of polynomial 13 | """ 14 | if backend is None: 15 | self._backend = check_backend(x) 16 | else: 17 | self._backend = backend 18 | initial = [lambda x: self._backend.ones_like(x), lambda x: x] 19 | recurrence = lambda p1, p2, n, x: self._backend.multiply(x, p1) - p2*n 20 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) 21 | 22 | 23 | class Hermite2(Poly): 24 | """ 25 | Hermite polynomials of the second kind (in Physics) 26 | """ 27 | def __init__(self, x, degree, backend = None, **kw): 28 | """ 29 | input: 30 | - x: a tensor 31 | - degree: highest degree of polynomial 32 | """ 33 | if backend is None: 34 | self._backend = check_backend(x) 35 | else: 36 | self._backend = backend 37 | initial = [lambda x: self._backend.ones_like(x), lambda x: x*2] 38 | recurrence = lambda p1, p2, n, x: (self._backend.multiply(x, p1) - p2*n)*2 39 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) -------------------------------------------------------------------------------- /orthnet/poly/_jacobi.py: -------------------------------------------------------------------------------- 1 | from ..utils import check_backend 2 | from .polynomial import Poly 3 | 4 | class Jacobi(Poly): 5 | """ 6 | Jacobi polynomials 7 | """ 8 | def __init__(self, x, degree, alpha, beta, backend = None, **kw): 9 | """ 10 | input: 11 | - x: a tensor 12 | - degree: highest degree of polynomial 13 | - alpha, beta: parameters for Jacobi polynomials 14 | """ 15 | if backend is None: 16 | self._backend = check_backend(x) 17 | else: 18 | self._backend = backend 19 | initial = [lambda x: self._backend.ones_like(x), lambda x: x*0.5*(alpha+beta+2)+0.5*(alpha-beta)] 20 | recurrence = lambda p1, p2, n, x: (self._backend.multiply(x*(2*n+alpha+beta)*(2*n+alpha+beta-2)+alpha**2-beta**2, p1)*(2*n+alpha+beta-1) - p2*(n+alpha-1)*(n+beta-1)*(2*n+alpha+beta)*2)/(2*n*(n+alpha+beta)*(2*n+alpha+beta-2)) 21 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) -------------------------------------------------------------------------------- /orthnet/poly/_laguerre.py: -------------------------------------------------------------------------------- 1 | from ..utils import check_backend 2 | from .polynomial import Poly 3 | 4 | class Laguerre(Poly): 5 | """ 6 | Laguerre polynomials 7 | """ 8 | def __init__(self, x, degree, backend = None, **kw): 9 | """ 10 | input: 11 | - x: a tensor 12 | - degree: highest degree of polynomial 13 | """ 14 | if backend is None: 15 | self._backend = check_backend(x) 16 | else: 17 | self._backend = backend 18 | initial = [lambda x: self._backend.ones_like(x), lambda x: 1-x] 19 | recurrence = lambda p1, p2, n, x: (self._backend.multiply(p1, 2*n+1-x)-p2*n)/(n+1) 20 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) -------------------------------------------------------------------------------- /orthnet/poly/_legendre.py: -------------------------------------------------------------------------------- 1 | from ..utils import check_backend 2 | from .polynomial import Poly 3 | from math import sqrt 4 | 5 | class Legendre(Poly): 6 | """ 7 | Legendre Polynomials 8 | """ 9 | def __init__(self, x, degree, backend = None, **kw): 10 | """ 11 | input: 12 | - x: a tensor 13 | - degree: highest degree of polynomial 14 | """ 15 | if backend is None: 16 | self._backend = check_backend(x) 17 | else: 18 | self._backend = backend 19 | initial = [lambda x: self._backend.ones_like(x), lambda x: x] 20 | recurrence = lambda p1, p2, n, x: (self._backend.multiply(x, p1)*(2*n+1)-p2*n)/(n+1) 21 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) 22 | 23 | 24 | class Legendre_Normalized(Poly): 25 | """ 26 | Normalized Legendre Polynomials with inner product be 1 if n == m. 27 | """ 28 | def __init__(self, x, degree, backend = None, **kw): 29 | """ 30 | input: 31 | - x: a tensor 32 | - degree: highest degree of polynomial 33 | """ 34 | if backend is None: 35 | self._backend = check_backend(x) 36 | else: 37 | self._backend = backend 38 | initial = [lambda x: self._backend.ones_like(x)*sqrt(1/2), lambda x: x*sqrt(3/2)] 39 | recurrence = lambda p1, p2, n, x: (self._backend.multiply(x, p1)*sqrt((2*n+1)*(2*n+3))-p2*n*sqrt((2*n+3)/(2*n-1)))/(n+1) 40 | Poly.__init__(self, self._backend, x, degree, initial, recurrence, **kw) -------------------------------------------------------------------------------- /orthnet/poly/polynomial.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from ..backend import TensorflowBackend, TorchBackend, NumpyBackend 3 | 4 | from ..utils import enum_dim, timeit 5 | 6 | 7 | def poly1d(x, degree, initial, recurrence): 8 | """ 9 | Generate 1d orthogonal dimensional polynomials via three-term recurrence 10 | 11 | Input: 12 | - x: argument tensor 13 | - degree: highest degree 14 | - initial: initial 2 polynomials f_0, f_1 15 | - recurrence: the recurrence relation, 16 | x_{n+1} = recurrence(x_{n}, x_{n-1}, n, x) 17 | 18 | Return: 19 | a list of polynomials from order 0 to order `degree` 20 | """ 21 | polys = [initial[0](x), initial[1](x)] 22 | if degree == 0: 23 | return [polys[0]] 24 | for i in range(1, degree): 25 | polys.append(recurrence(polys[-1], polys[-2], i, x)) 26 | return polys 27 | 28 | 29 | class Poly(object): 30 | def __init__(self, backend, x, degree, initial, recurrence, combinations = None): 31 | self._x = x 32 | self._degree = degree 33 | self._initial = initial 34 | self._recurrence = recurrence 35 | self._backend = backend 36 | self._dim = self._backend.get_dims(self._x)[1] 37 | 38 | if combinations is not None: 39 | self._index_combinations = combinations 40 | else: 41 | self._index_combinations = enum_dim(self._degree, self._dim) 42 | self._index = self._index_combinations[0] 43 | self._combinations = self._index_combinations[1:] 44 | 45 | self._poly1d = [poly1d(x[:, i], self._degree, self._initial, self._recurrence) for i in range(self._dim)] 46 | self._list = [] 47 | 48 | def _compute(self, start, end): 49 | polynomials = [] 50 | for comb in self._combinations[self._index[start]:(None if end == self._degree else self._index[end]+1)]: 51 | poly = self._backend.ones_like(self._x[:, 0]) 52 | for i in range(len(comb)): 53 | poly = self._backend.multiply(poly, self._poly1d[i][comb[i]]) 54 | poly = self._backend.expand_dims(poly, axis = 1) 55 | polynomials.append(poly) 56 | return polynomials 57 | 58 | @property 59 | def list(self): 60 | if not self._list: 61 | self._list = self._compute(0, self._degree) 62 | return self._list 63 | 64 | @property 65 | def tensor(self): 66 | return self._backend.concatenate(self.list, axis = 1) 67 | 68 | 69 | def update(self, new_degree): 70 | if new_degree > self._degree: 71 | original_degree = self._degree 72 | self._degree = new_degree 73 | self._combinations = enum_dim(self._degree, self._dim) 74 | self._index = self._combinations[0] 75 | self._combinations = self._combinations[1:] 76 | self._poly1d = [poly1d(x[:, i], self._degree, self._initial, self._recurrence) for i in range(self._dim)] 77 | self._list.extend(self._compute(original_degree+1, self._degree)) 78 | 79 | @property 80 | def combinations(self): 81 | return self._combinations 82 | 83 | @property 84 | def index(self): 85 | return self._index 86 | 87 | @property 88 | def length(self): 89 | return len(self._combinations) 90 | 91 | def _get_poly(self, start, end): 92 | """ 93 | get polynomials from degree `start`(included) to `end`(included) 94 | """ 95 | assert start >= 0 and end <= self._degree, "Degree should be less or equal than highest degree" 96 | return self.tensor[:, self._index[start]:(None if end == self._degree else self._index[end+1])] 97 | 98 | def tensor_of_degree(self, degree): 99 | """ 100 | degree: a number or iterator of target degrees 101 | """ 102 | if isinstance(degree, int): 103 | degree = [degree] 104 | return self._get_poly(degree[0], degree[-1]) 105 | 106 | def eval(self, coefficients): 107 | shape = self._backend.get_dims(coefficients) 108 | if len(shape) == 1: 109 | coefficients = self._backend.reshape(coefficients, (-1, 1)) 110 | return self._backend.matmul(self.tensor, coefficients) 111 | 112 | def quadrature(self, function, weight): 113 | shape = self._backend.get_dims(weight) 114 | if len(shape) == 1: 115 | weight = self._backend.reshape(weight, (1, -1)) 116 | return self._backend.matmul(weight, self._backend.multiply(function(self._x), self.tensor)) -------------------------------------------------------------------------------- /orthnet/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .timeit import timeit 2 | from ._enum_dim import enum_dim 3 | from .check_backend import check_backend 4 | 5 | __all__ = [enum_dim, timeit, check_backend] -------------------------------------------------------------------------------- /orthnet/utils/check_backend.py: -------------------------------------------------------------------------------- 1 | from ..backend import NumpyBackend, TensorflowBackend, TorchBackend 2 | 3 | def check_backend(x): 4 | _all_backends = list(filter(lambda backend: backend.is_available(), [TensorflowBackend(), TorchBackend(), NumpyBackend()])) 5 | for backend in _all_backends: 6 | if backend.is_compatible(x): 7 | return backend 8 | raise TypeError("Cannot determine backend from input arguments of type `{1}`. Available backends are {2}".format(type(self.x), ", ".join([str(backend) for backend in _all_backends]))) -------------------------------------------------------------------------------- /orthnet/utils/enum_dim/compile.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | swig -python -c++ enum_dim.i 3 | g++ -Ofast -c -fPIC enum_dim_wrap.cxx -I/usr/include/python3.6 4 | g++ -Ofast -shared enum_dim_wrap.o -o _enum_dim.so -------------------------------------------------------------------------------- /orthnet/utils/enum_dim/enum_dim.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void dfs(std::vector >& res, std::vector cur, int n, int dim) { 4 | if(dim == 1) { 5 | cur.push_back(n); 6 | res.push_back(cur); 7 | return; 8 | } 9 | for(int i = n; i >= 0; i--) { 10 | cur.push_back(i); 11 | dfs(res, cur, n-i, dim-1); 12 | cur.pop_back(); 13 | } 14 | } 15 | 16 | std::vector > enum_dim(int n, int dim) { 17 | /* 18 | enumerate dims; 19 | 20 | input: 21 | n: total order 22 | dim: dimension (number of variables) 23 | 24 | output: 25 | a 2D-list, the first element is the index of each degree, others a combination (a tuple) 26 | */ 27 | std::vector > res; 28 | res.push_back(std::vector()); 29 | for(int i = 0; i <= n; i++) { 30 | res[0].push_back(res.size()-1); 31 | dfs(res, std::vector(), i, dim); 32 | } 33 | return res; 34 | } -------------------------------------------------------------------------------- /orthnet/utils/enum_dim/enum_dim.i: -------------------------------------------------------------------------------- 1 | %module enum_dim 2 | %{ 3 | #include "enum_dim.h" 4 | %} 5 | 6 | %include "std_vector.i" 7 | %template() std::vector; 8 | %template() std::vector >; 9 | 10 | %include "enum_dim.h" -------------------------------------------------------------------------------- /orthnet/utils/enum_dim/poly_class.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import torch 3 | from multi_dim import enumerate_dim as enumerate_dim 4 | from _enum_dim import enum_dim as enum_dim_cpp 5 | 6 | 7 | class Poly1d: 8 | """ 9 | Base class, 1-dimensional orthogonal polynomials by three-term recursion. 10 | """ 11 | def __init__(self, module, degree, x, initial, recurrence): 12 | """ 13 | - input: 14 | - module: 'tensorflow' or 'pytorch' (case insensitive) 15 | - degree: degree of polynomial 16 | - x: tensor for evaluating function values (should have dimension >= 2) 17 | - initial: initial value, a list of two functions 18 | - recurrence: the function of recurrence, with three variables: 19 | P_{n+1} = f(P_{n}, P_{n-1}, n, x) 20 | (initial and recurrence are functions on Tensors) 21 | """ 22 | self.module = module.lower() 23 | assert self.module in ['tensorflow', 'pytorch'], "Module should be either 'tensorflow' or 'pytorch'." 24 | assert n >= 0 and isinstance(n, int), "Degree should be a non-negative integer." 25 | assert len(initial) == 2, "Need two initial functions." 26 | if self.module == 'tensorflow': 27 | assert isinstance(x, tf.Variable) or isinstance(x, tf.Tensor), "x should be an isinstance of tensorflow.Variable or tensorflow.Tensor." 28 | else: 29 | assert isinstance(x, torch.autograd.Variable) or isinstance(x, torch.Tensor), "x should be an isinstance of torch.autograd.Variable or torch.Tensor." 30 | self.degree = degree 31 | self.x = x 32 | self.initial = initial 33 | self.recurrence = recurrence 34 | self.poly_list = [self.initial[0](self.x), self.initial[1](self.x)] 35 | 36 | def _compute(self, start, end): 37 | if end == 0: 38 | return [self.initial[0](self.x)] 39 | elif end == 1: 40 | return [self.initial[0](self.x), self.initial[1](self.x)] 41 | else: 42 | polys = [] 43 | for i in range(start, end+1): 44 | 45 | def update(self, new_degree): 46 | self. 47 | 48 | 49 | @property 50 | def list(self): 51 | """ 52 | generate a list of function values in lexicographical order. 53 | 54 | output: 55 | [[p0(x1), p0(x2), .., p0(xm)], [p1(x1), p1(x2), .., p1(xm)], .., [pn(x1), pn(x2), .., pn(xm)]] 56 | """ 57 | if self.poly_list: 58 | return self.poly_list 59 | if self.order == 0: 60 | self.poly_list = [initial[0](self.x)] 61 | elif self.order == 1: 62 | self.poly_list = [initial[0](x), initial[1](x)] 63 | else: 64 | self.poly_list = [initial[0](x), initial[1](x)] 65 | for i in range(1, n): 66 | self.poly_list.append(self.recurrence(self.poly_list[-1], self.poly_list[-2], i, self.x)) 67 | return self.poly_list 68 | 69 | @property 70 | def tensor(self): 71 | """ 72 | generate a tensor of function values in lexicographical order. 73 | 74 | output: 75 | Tensor([[p0(x)], [p1(x)], .., [pn(x)]]) 76 | """ 77 | if self.module == 'tensorflow': 78 | return tf.concat(self.polys_list, axis = 1) 79 | else: 80 | return torch.cat(self.polys_list, dim = 1) 81 | 82 | 83 | 84 | class Poly: 85 | """ 86 | Base class, multi-dimensional orthogonal polynomials by three-term recursion and tensor product. 87 | """ 88 | def __init__(self, module, n, x, initial, recurrence): 89 | """ 90 | input: 91 | - module: 'tensorflow' or 'pytorch' 92 | - n: order of target polynomial 93 | - x: a tensor, each row is a sample point, and each column is a feature (or variable). 94 | - initial: initial value, a list of two functions 95 | - recurrence: the function of recurrence, with three variables: 96 | P_{n+1} = f(P_{n}, P_{n-1}, n, x) 97 | (initial and recurrence are functions on Tensors) 98 | 99 | output: 100 | - y: a list of function values 101 | """ 102 | self.module = module.lower() 103 | assert self.module in ['tensorflow', 'pytorch'], "Module should be either 'tensorflow' or 'pytorch'." 104 | assert n >= 0 and isinstance(n, int), "Order should be a non-negative integer." 105 | assert len(initial) == 2, "Need two initial functions." 106 | self.n = n 107 | self.x = x 108 | self.initial = initial 109 | self.recurrence = recurrence 110 | self.dims = [] 111 | if self.module == 'tensorflow': 112 | self.n_features 113 | 114 | @property 115 | def list(self): 116 | """ 117 | generate a list of half tensor product of function values in lexicographical order. 118 | 119 | example: 120 | >>> x = Poly(_, 2, [x, y], _) 121 | >>> x.polys_list 122 | >>> [[p0(x)p0(y)], [p1(x)p0(y)], [p0(x)p1(y)], [p2(x)p0(y)], [p1(x)p1(y)], [p0(x)p2(y)]] 123 | """ 124 | one_dim_polys, polys = [], [] 125 | if 126 | # for index in range() 127 | # tmp = Poly1d(self.module, self.n, var, self.initial, self.recurrence) 128 | # one_dim_polys.append(tmp.list) 129 | if not self.dims: 130 | for i in range(n+1): 131 | self.dims.append(enumerate_dim_cpp()) 132 | 133 | 134 | 135 | 136 | # class Poly(Poly1d): 137 | # """ 138 | # Base class, multi-dimensional orthogonal polynomials by three-term recursion and tensor product. 139 | # """ 140 | # def __init__(self, module, n, x, initial, recurrence): 141 | # """ 142 | # input: 143 | # - module: 'tensorflow' or 'pytorch' 144 | # - n: order of target polynomial 145 | # - x: a list of tensors (as variables) 146 | # - initial: initial value, a list of two functions 147 | # - recurrence: the function of recurrence, with three variables: 148 | # P_{n+1} = f(P_{n}, P_{n-1}, n, x) 149 | # (initial and recurrence are functions on Tensors) 150 | 151 | # output: 152 | # - y: a list of function values 153 | # """ 154 | # assert isinstance(x, list) or isinstance(x, tuple), "x should be a list or a tuple of tensors." 155 | # super().__init__(self, module, n, x[0], initial, recurrence) 156 | # self.x = x # Override super.x 157 | 158 | # @property 159 | # def list(self): 160 | # """ 161 | # generate a list of half tensor product of function values in lexicographical order. 162 | 163 | # example: 164 | # >>> x = Poly(_, 2, [x, y], _) 165 | # >>> x.polys_list 166 | # >>> [[p0(x)p0(y)], [p1(x)p0(y)], [p0(x)p1(y)], [p2(x)p0(y)], [p1(x)p1(y)], [p0(x)p2(y)]] 167 | # """ 168 | # one_dim_polys, polys = [], [] 169 | # for var in self.x: 170 | # tmp = Poly1d(self.module, n, x, initial, recurrence) 171 | # one_dim_polys.append() -------------------------------------------------------------------------------- /orthnet/utils/timeit.py: -------------------------------------------------------------------------------- 1 | import time 2 | from functools import wraps 3 | 4 | def timeit(loglevel): 5 | def _timeit(func): 6 | @wraps(func) 7 | def timed(*args, **kw): 8 | t1 = time.time() 9 | res = func(*args, **kw) 10 | print(func.__name__, (time.time() - t1)) 11 | return res 12 | def untimed(*args, **kw): 13 | return func(*args, **kw) 14 | if loglevel == 0: 15 | return untimed 16 | elif loglevel == 1: 17 | return timed 18 | return _timeit -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Extension 2 | 3 | ext = Extension('orthnet.utils._enum_dim', 4 | sources = ['orthnet/utils/enum_dim/enum_dim.i'], 5 | language = 'c++', 6 | swig_opts = ['-c++'], 7 | extra_compile_args = [ 8 | '-std=c++11', 9 | '-Wall', 10 | '-Ofast', 11 | ] 12 | ) 13 | 14 | setup( 15 | name = 'orthnet', 16 | version = '0.4.0', 17 | keywords = ['orthogonal polynomial', 'tensorflow', 'pytorch', 'numpy'], 18 | description = 'TensorFlow, PyTorch and Numpy layers for generating orthogonal polynomials', 19 | license = 'MIT', 20 | author = 'Chuan Lu', 21 | author_email = 'chuan-lu@uiowa.edu', 22 | ext_modules = [ext], 23 | packages = ['orthnet', 'orthnet.poly', 'orthnet.utils', 'orthnet.backend'], 24 | ) --------------------------------------------------------------------------------