/* Copyright 2019 The fast_sparse_interpolation Authors. All Rights Reserved. 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. ==============================================================================*/ #pragma once #include #include #include #include namespace fsi { size_t binom(size_t n, size_t k) { if (2 * k > n) { k = n - k; } size_t prod = 1; size_t upper_factor = n + 1 - k; size_t lower_factor = 1; while (upper_factor <= n) { prod *= upper_factor; prod /= lower_factor; upper_factor += 1; lower_factor += 1; } return prod; } template class TemplateBoundedSumIterator { size_t bound; std::vector index_head; // contains all entries of the index except the last one size_t index_head_sum; bool is_done; public: TemplateBoundedSumIterator(size_t bound) : bound(bound), index_head(d - 1, 0), index_head_sum(0), is_done(false){}; /** * At the current multi-index (i_1, ..., i_{d-1}, 0), return how many multi-indices starting with * (i_1, ..., i_{d-1}) are contained in the multi-index set, then advance to the next multi-index * that ends with a zero. */ size_t lastDimensionCount() { return bound - index_head_sum + 1; } void next() { if (bound > index_head_sum) { index_head_sum += 1; index_head[d - 2] += 1; } else { int dim = d - 2; for (; dim >= 0 && index_head[dim] == 0; --dim) { // reduce dimension until entry is nonzero } if (dim > 0) { index_head_sum -= (index_head[dim] - 1); index_head[dim] = 0; index_head[dim - 1] += 1; } else if (dim == 0) { index_head[dim] = 0; index_head_sum = 0; is_done = true; } } } size_t firstIndex() const { return index_head[0]; } size_t indexAt(size_t dim) const { return index_head[dim]; } bool done() const { return is_done; } void reset() { index_head = std::vector(d - 1, 0); index_head_sum = 0; is_done = false; } size_t dim() const { return d; } std::vector indexBounds() const { return std::vector(d, bound + 1); } size_t numValues() const { return binom(bound + d, d); } /** * Returns an iterator where the last index moves to the front. For an index set defined by a sum * bound, nothing changes. */ TemplateBoundedSumIterator cycle() const { TemplateBoundedSumIterator it = *this; it.reset(); return it; } }; class BoundedSumIterator { size_t d; size_t bound; std::vector index_head; // contains all entries of the index except the last one size_t index_head_sum; bool is_done; public: BoundedSumIterator(size_t d, size_t bound) : d(d), bound(bound), index_head(d - 1, 0), index_head_sum(0), is_done(false){}; /** * At the current multi-index (i_1, ..., i_{d-1}, 0), return how many multi-indices starting with * (i_1, ..., i_{d-1}) are contained in the multi-index set, then advance to the next multi-index * that ends with a zero. */ size_t lastDimensionCount() { return bound - index_head_sum + 1; } void next() { if (bound > index_head_sum) { index_head_sum += 1; index_head[d - 2] += 1; } else { int dim = d - 2; for (; dim >= 0 && index_head[dim] == 0; --dim) { // reduce dimension until entry is nonzero } if (dim > 0) { index_head_sum -= (index_head[dim] - 1); index_head[dim] = 0; index_head[dim - 1] += 1; } else if (dim == 0) { index_head[dim] = 0; index_head_sum = 0; is_done = true; } } } size_t firstIndex() const { return index_head[0]; } size_t indexAt(size_t dim) const { return index_head[dim]; } bool done() const { return is_done; } void reset() { index_head = std::vector(d - 1, 0); index_head_sum = 0; is_done = false; } size_t dim() const { return d; } std::vector indexBounds() const { return std::vector(d, bound + 1); } size_t numValues() const { return binom(bound + d, d); } /** * Returns an iterator where the last index moves to the front. For an index set defined by a sum * bound, nothing changes. */ BoundedSumIterator cycle() { BoundedSumIterator it = *this; it.reset(); return it; } }; class StandardBoundedSumIterator { size_t d; size_t bound; std::vector index; // contains all entries of the index except the last one size_t index_sum; bool is_done; public: StandardBoundedSumIterator(size_t d, size_t bound) : d(d), bound(bound), index(d, 0), index_sum(0), is_done(false){}; /** * At the current multi-index (i_1, ..., i_{d-1}, 0), return how many multi-indices starting with * (i_1, ..., i_{d-1}) are contained in the multi-index set, then advance to the next multi-index * that ends with a zero. */ bool next() { if (bound > index_sum) { index_sum += 1; index[d - 1] += 1; } else { int dim = d - 1; for (; dim >= 0 && index[dim] == 0; --dim) { // reduce dimension until entry is nonzero } if (dim > 0) { index_sum -= index[dim]; index[dim] = 0; index[dim - 1] += 1; } else if (dim == 0) { index[dim] = 0; index_sum = 0; is_done = true; } } return is_done; } size_t firstIndex() { return index[0]; } size_t indexSum() { return index_sum; } bool done() { return is_done; } void reset() { index = std::vector(d - 1, 0); index_sum = 0; is_done = false; } }; class MultiDimVector { public: // put the first dimension into an outer vector for processing reasons std::vector> data; MultiDimVector(size_t n) : data(n){}; void swap(MultiDimVector &other) { data.swap(other.data); } void clear() { for (size_t dim = 0; dim < data.size(); ++dim) { data[dim].clear(); } } }; template void multiply_lower_triangular_inplace(It it, std::vector> L, MultiDimVector &v) { // the multiplication is based on a cyclic permutation of the indices: the last index of v becomes // the first index of w size_t d = L.size(); auto n = it.indexBounds(); for (int k = d - 1; k >= 0; --k) { MultiDimVector w(n[k]); for (size_t idx = 0; idx < n[k]; ++idx) { // TODO: make compatible with other iterators w.data[idx].reserve(v.data[idx].size()); } auto &Lk = L[k]; it.reset(); size_t first_v_index = 0; size_t second_v_index = 0; double *data_pointer = &v.data[0][0]; size_t data_size = v.data[0].size(); while (not it.done()) { size_t last_dim_count = it.lastDimensionCount(); double *offset_data_pointer = data_pointer + second_v_index; for (size_t i = 0; i < last_dim_count; ++i) { double sum = 0.0; for (size_t j = 0; j <= i; ++j) { sum += Lk(i, j) * (*(offset_data_pointer + j)); } w.data[i].push_back(sum); } second_v_index += last_dim_count; if (second_v_index >= data_size) { second_v_index = 0; first_v_index += 1; data_pointer = &v.data[first_v_index][0]; data_size = v.data[first_v_index].size(); } it.next(); } v.swap(w); it = it.cycle(); // for (size_t i = 0; i < v.data.size(); ++i) { // std::cout << v.data[i] << "\n\n"; // } } } template void multiply_upper_triangular_inplace(It it, std::vector> U, MultiDimVector &v) { // the multiplication is based on a cyclic permutation of the indices: the last index of v becomes // the first index of w size_t d = U.size(); auto n = it.indexBounds(); for (int k = d - 1; k >= 0; --k) { MultiDimVector w(n[k]); for (size_t idx = 0; idx < n[k]; ++idx) { // TODO: make compatible with other iterators w.data[idx].reserve(v.data[idx].size()); } auto &Uk = U[k]; it.reset(); size_t first_v_index = 0; size_t second_v_index = 0; double *data_pointer = &v.data[0][0]; size_t data_size = v.data[0].size(); while (not it.done()) { size_t last_dim_count = it.lastDimensionCount(); double *offset_data_pointer = data_pointer + second_v_index; for (size_t i = 0; i < last_dim_count; ++i) { double sum = 0.0; for (size_t j = i; j < last_dim_count; ++j) { sum += Uk(i, j) * (*(offset_data_pointer + j)); } w.data[i].push_back(sum); } second_v_index += last_dim_count; if (second_v_index >= data_size) { second_v_index = 0; first_v_index += 1; data_pointer = &v.data[first_v_index][0]; data_size = v.data[first_v_index].size(); } it.next(); } it = it.cycle(); v.swap(w); // for (size_t i = 0; i < v.data.size(); ++i) { // std::cout << v.data[i] << "\n\n"; // } } } /** * Represents a sparse linear tensor product operator defined by a matrix for each dimension. */ template class SparseTPOperator { It it; std::vector> M; std::vector> LU; std::vector> L; std::vector> U; std::vector> Linv; std::vector> Uinv; size_t d; public: SparseTPOperator(It it, std::vector> matrices) : it(it), M(matrices), d(matrices.size()){}; void prepareCommon() { if (LU.size() > 0) { return; // already prepared } namespace ublas = boost::numeric::ublas; typedef ublas::matrix Matrix; for (size_t k = 0; k < d; ++k) { // std::cout << "Matrix creation loop\n"; Matrix Mk = M[k]; ublas::lu_factorize(Mk); LU.push_back(Mk); } } void prepareApply() { if (L.size() > 0) { return; // already prepared } prepareCommon(); namespace ublas = boost::numeric::ublas; typedef ublas::matrix Matrix; for (size_t k = 0; k < d; ++k) { size_t nk = M[k].size1(); Matrix &LUk = LU[k]; Matrix Lk(nk, nk); Matrix Uk(nk, nk); for (size_t i = 0; i < nk; ++i) { for (size_t j = 0; j < i; ++j) { Lk(i, j) = LUk(i, j); } Lk(i, i) = 1.0; for (size_t j = i; j < nk; ++j) { Uk(i, j) = LUk(i, j); } } L.push_back(Lk); U.push_back(Uk); } } void prepareSolve() { if (Linv.size() > 0) { return; // already prepared } prepareCommon(); namespace ublas = boost::numeric::ublas; typedef ublas::matrix Matrix; for (size_t k = 0; k < d; ++k) { Matrix Lkinv = ublas::identity_matrix(M[k].size1()); Matrix Ukinv = ublas::identity_matrix(M[k].size1()); ublas::inplace_solve(LU[k], Lkinv, ublas::unit_lower_tag()); ublas::inplace_solve(LU[k], Ukinv, ublas::upper_tag()); Linv.push_back(Lkinv); Uinv.push_back(Ukinv); } } MultiDimVector apply(MultiDimVector input) { prepareApply(); multiply_upper_triangular_inplace(it, U, input); multiply_lower_triangular_inplace(it, L, input); return input; } MultiDimVector solve(MultiDimVector rhs) { prepareSolve(); multiply_lower_triangular_inplace(it, Linv, rhs); multiply_upper_triangular_inplace(it, Uinv, rhs); return rhs; } }; template SparseTPOperator createInterpolationOperator(It it, Phi phi, X x) { auto n = it.indexBounds(); size_t d = it.dim(); namespace ublas = boost::numeric::ublas; typedef ublas::matrix Matrix; std::vector matrices; // create matrices and inverted LU decompositions for (size_t k = 0; k < d; ++k) { // std::cout << "Matrix creation loop\n"; Matrix Mk(n[k], n[k]); for (size_t i = 0; i < n[k]; ++i) { for (size_t j = 0; j < n[k]; ++j) { Mk(i, j) = phi[k](j)(x[k](i)); } } matrices.push_back(Mk); } return SparseTPOperator(it, matrices); } template MultiDimVector evaluateFunction(It it, Func f, X x) { size_t d = it.dim(); auto n = it.indexBounds(); MultiDimVector v(n[0]); it.reset(); std::vector point(d); while (not it.done()) { size_t last_dim_count = it.lastDimensionCount(); for (size_t dim = 0; dim < d - 1; ++dim) { point[dim] = x[dim](it.indexAt(dim)); } for (size_t last_dim_idx = 0; last_dim_idx < last_dim_count; ++last_dim_idx) { point[d - 1] = x[d - 1](last_dim_idx); double function_value = f(point); v.data[it.firstIndex()].push_back(function_value); } it.next(); } return v; } inline std::ostream &operator<<(std::ostream &os, boost::numeric::ublas::matrix matrix) { os << "["; for (size_t i = 0; i < matrix.size1(); ++i) { for (size_t j = 0; j < matrix.size2(); ++j) { os << matrix(i, j) << " "; } os << "\n"; } os << " ]"; return os; } template inline std::ostream &operator<<(std::ostream &os, const std::vector &v) { os << "["; for (auto ii = v.begin(); ii != v.end(); ++ii) { os << " " << *ii; } os << " ]"; return os; } template MultiDimVector interpolate(Func f, It it, Phi phi, X x) { auto n = it.indexBounds(); size_t d = it.dim(); namespace ublas = boost::numeric::ublas; typedef ublas::matrix Matrix; std::vector Linv, Uinv; // create matrices and inverted LU decompositions for (size_t k = 0; k < d; ++k) { // std::cout << "Matrix creation loop\n"; Matrix Mk(n[k], n[k]); for (size_t i = 0; i < n[k]; ++i) { for (size_t j = 0; j < n[k]; ++j) { Mk(i, j) = phi[k](j)(x[k](i)); } } // std::cout << "Matrices:\n"; // std::cout << Mk << "\n"; ublas::lu_factorize(Mk); // std::cout << Mk << "\n"; Matrix Lkinv = ublas::identity_matrix(n[k]); Matrix Ukinv = ublas::identity_matrix(n[k]); ublas::inplace_solve(Mk, Lkinv, ublas::unit_lower_tag()); ublas::inplace_solve(Mk, Ukinv, ublas::upper_tag()); // std::cout << Lkinv << "\n"; // std::cout << Ukinv << "\n"; Linv.push_back(Lkinv); Uinv.push_back(Ukinv); } MultiDimVector v(n[0]); std::cout << "Compute function values\n"; // compute function values it.reset(); std::vector point(d); while (not it.done()) { size_t last_dim_count = it.lastDimensionCount(); for (size_t dim = 0; dim < d - 1; ++dim) { point[dim] = x[dim](it.indexAt(dim)); } for (size_t last_dim_idx = 0; last_dim_idx < last_dim_count; ++last_dim_idx) { point[d - 1] = x[d - 1](last_dim_idx); double function_value = f(point); v.data[it.firstIndex()].push_back(function_value); } it.next(); } size_t number = 0; for (size_t dim = 0; dim < n[0]; ++dim) { number += v.data[dim].size(); } std::cout << "number of points: " << number << "\n"; std::cout << "alternative number of points: " << it.numValues() << "\n"; // for (size_t i = 0; i < v.data.size(); ++i) { // std::cout << v.data[i] << "\n\n"; // } std::cout << "First matrix multiplication\n"; // multiply by L^{-1} multiply_lower_triangular_inplace(it, Linv, v); std::cout << "Second matrix multiplication\n"; // multiply by U^{-1} // the multiplication is based on a cyclic permutation of the indices: the last index of v becomes // the first index of w multiply_upper_triangular_inplace(it, Uinv, v); return v; } } /* namespace fsi */